@libraz/libcantus 0.9.2 → 0.9.4
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 +6 -5
- package/dist/analyze/index.cjs +41 -1676
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +194 -122
- package/dist/analyze/index.d.ts +194 -122
- package/dist/analyze/index.js +19 -8
- package/dist/chunk-36N73KZ3.cjs +246 -0
- package/dist/chunk-36N73KZ3.cjs.map +1 -0
- package/dist/chunk-4HLLZPXT.cjs +3077 -0
- package/dist/chunk-4HLLZPXT.cjs.map +1 -0
- package/dist/chunk-7MGZ7D62.cjs +498 -0
- package/dist/chunk-7MGZ7D62.cjs.map +1 -0
- package/dist/{chunk-R3S2LUBH.js → chunk-7NKDPF74.js} +660 -305
- package/dist/chunk-7NKDPF74.js.map +1 -0
- package/dist/chunk-CAN76NCU.cjs +679 -0
- package/dist/chunk-CAN76NCU.cjs.map +1 -0
- package/dist/{chunk-7Y7WF64D.js → chunk-D2FAJBYL.js} +104 -35
- package/dist/chunk-D2FAJBYL.js.map +1 -0
- package/dist/{chunk-COKTX2G5.js → chunk-EHVEBWM5.js} +1052 -510
- package/dist/chunk-EHVEBWM5.js.map +1 -0
- package/dist/chunk-FCANXBAT.cjs +30 -0
- package/dist/chunk-FCANXBAT.cjs.map +1 -0
- package/dist/chunk-FNDWXRRI.cjs +479 -0
- package/dist/chunk-FNDWXRRI.cjs.map +1 -0
- package/dist/{chunk-GQKN5KDT.js → chunk-GQSGS2N4.js} +43 -29
- package/dist/chunk-GQSGS2N4.js.map +1 -0
- package/dist/chunk-HKGYCQ34.js +53 -0
- package/dist/chunk-HKGYCQ34.js.map +1 -0
- package/dist/chunk-I24MV65U.js +106 -0
- package/dist/chunk-I24MV65U.js.map +1 -0
- package/dist/{chunk-LN7ZZMD2.js → chunk-KMHOICIY.js} +322 -173
- package/dist/chunk-KMHOICIY.js.map +1 -0
- package/dist/chunk-LW53AHQU.cjs +1117 -0
- package/dist/chunk-LW53AHQU.cjs.map +1 -0
- package/dist/chunk-N6IHQUOX.js +1 -0
- package/dist/chunk-N6IHQUOX.js.map +1 -0
- package/dist/chunk-NKIBQ6PT.js +30 -0
- package/dist/chunk-NKIBQ6PT.js.map +1 -0
- package/dist/chunk-NP4NA2FB.cjs +106 -0
- package/dist/chunk-NP4NA2FB.cjs.map +1 -0
- package/dist/chunk-NVE34YGZ.cjs +1449 -0
- package/dist/chunk-NVE34YGZ.cjs.map +1 -0
- package/dist/{chunk-A4532ZFR.js → chunk-NYYWMV2C.js} +115 -47
- package/dist/chunk-NYYWMV2C.js.map +1 -0
- package/dist/{chunk-5HTNMQW4.js → chunk-Q2XUP227.js} +310 -118
- package/dist/chunk-Q2XUP227.js.map +1 -0
- package/dist/chunk-Q6OP4U43.cjs +327 -0
- package/dist/chunk-Q6OP4U43.cjs.map +1 -0
- package/dist/chunk-RC6GBMCD.cjs +845 -0
- package/dist/chunk-RC6GBMCD.cjs.map +1 -0
- package/dist/{chunk-XTC3X6MS.js → chunk-RK35RIN7.js} +453 -152
- package/dist/chunk-RK35RIN7.js.map +1 -0
- package/dist/chunk-RUMU3UA6.cjs +53 -0
- package/dist/chunk-RUMU3UA6.cjs.map +1 -0
- package/dist/chunk-T64C7ZD7.cjs +1 -0
- package/dist/chunk-T64C7ZD7.cjs.map +1 -0
- package/dist/chunk-XEBG3G53.js +498 -0
- package/dist/chunk-XEBG3G53.js.map +1 -0
- package/dist/chunk-YLEMGEFH.js +246 -0
- package/dist/chunk-YLEMGEFH.js.map +1 -0
- package/dist/chunk-ZEJR4TKL.cjs +173 -0
- package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
- package/dist/core/index.cjs +130 -431
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +331 -17
- package/dist/core/index.d.ts +331 -17
- package/dist/core/index.js +71 -13
- package/dist/{function-2bJW1Zp_.d.ts → function-BtiZb9i9.d.ts} +28 -7
- package/dist/{function-r2sh-jOO.d.cts → function-DNugAOrt.d.cts} +28 -7
- package/dist/generate/index.cjs +30 -3751
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +373 -59
- package/dist/generate/index.d.ts +373 -59
- package/dist/generate/index.js +23 -9
- package/dist/{index-DZ1M21do.d.cts → index-6VR8-kit.d.ts} +20 -4
- package/dist/index-70p168VM.d.ts +192 -0
- package/dist/{index-D_ZTUNKp.d.cts → index-Be74mcon.d.cts} +18 -17
- package/dist/index-BtXT6UQ0.d.cts +192 -0
- package/dist/{index-BMPxwjwV.d.ts → index-C5UBbQ4G.d.ts} +18 -17
- package/dist/{index-BOBXBNwP.d.cts → index-C83mZI1P.d.ts} +93 -8
- package/dist/{index-BdeQ1r_p.d.cts → index-D6b6na69.d.cts} +14 -11
- package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
- package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
- package/dist/index-OG65ocOr.d.cts +272 -0
- package/dist/index-OG65ocOr.d.ts +272 -0
- package/dist/{index-BreoJRw0.d.ts → index-tJhkmQgE.d.cts} +20 -4
- package/dist/{index-CP3wAfEH.d.ts → index-tSQh-xwY.d.ts} +14 -11
- package/dist/{index-CM5W4rcm.d.ts → index-y_ak9_Hi.d.cts} +93 -8
- package/dist/index.cjs +419 -6848
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -13
- package/dist/index.d.ts +14 -13
- package/dist/index.js +132 -27
- package/dist/model/index.cjs +8 -2423
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +433 -74
- package/dist/model/index.d.ts +433 -74
- package/dist/model/index.js +6 -7
- package/dist/styled-DIRkk9Uz.d.ts +506 -0
- package/dist/styled-DJbkJQfj.d.cts +506 -0
- package/dist/theory/index.cjs +172 -1676
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +151 -233
- package/dist/theory/index.d.ts +151 -233
- package/dist/theory/index.js +31 -7
- package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
- package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
- package/package.json +73 -24
- package/dist/chunk-5HTNMQW4.js.map +0 -1
- package/dist/chunk-7Y7WF64D.js.map +0 -1
- package/dist/chunk-A4532ZFR.js.map +0 -1
- package/dist/chunk-BHUT2S5G.js +0 -22
- package/dist/chunk-BHUT2S5G.js.map +0 -1
- package/dist/chunk-CCBLVAPK.js +0 -42
- package/dist/chunk-CCBLVAPK.js.map +0 -1
- package/dist/chunk-COKTX2G5.js.map +0 -1
- package/dist/chunk-DYWFNC4A.js +0 -152
- package/dist/chunk-DYWFNC4A.js.map +0 -1
- package/dist/chunk-GQKN5KDT.js.map +0 -1
- package/dist/chunk-LN7ZZMD2.js.map +0 -1
- package/dist/chunk-MSWFZF7J.js +0 -118
- package/dist/chunk-MSWFZF7J.js.map +0 -1
- package/dist/chunk-R3S2LUBH.js.map +0 -1
- package/dist/chunk-SDM3VJJ5.js +0 -41
- package/dist/chunk-SDM3VJJ5.js.map +0 -1
- package/dist/chunk-XTC3X6MS.js.map +0 -1
- package/dist/chunk-ZXW5YIWK.js +0 -55
- package/dist/chunk-ZXW5YIWK.js.map +0 -1
- package/dist/index-CGMZNCx6.d.cts +0 -140
- package/dist/index-CGMZNCx6.d.ts +0 -140
- package/dist/styled-BjXtOFNk.d.cts +0 -282
- package/dist/styled-D7BAx3S3.d.ts +0 -282
- package/src/analyze/arrange/index.ts +0 -21
- package/src/analyze/arrange/internal.ts +0 -125
- package/src/analyze/arrange/tension.ts +0 -187
- package/src/analyze/arrange/tracks.ts +0 -279
- package/src/analyze/detect/index.ts +0 -235
- package/src/analyze/functional/borrowed.ts +0 -84
- package/src/analyze/functional/cadence.ts +0 -66
- package/src/analyze/functional/function.ts +0 -184
- package/src/analyze/functional/index.ts +0 -23
- package/src/analyze/functional/internal.ts +0 -29
- package/src/analyze/functional/roman.ts +0 -393
- package/src/analyze/index.ts +0 -49
- package/src/analyze/timeline/index.ts +0 -415
- package/src/analyze/voice/index.ts +0 -330
- package/src/core/index.ts +0 -47
- package/src/core/interval/index.ts +0 -81
- package/src/core/meter/index.ts +0 -282
- package/src/core/pitch/index.ts +0 -311
- package/src/core/random/index.ts +0 -53
- package/src/core/tuning/index.ts +0 -156
- package/src/core/types.ts +0 -29
- package/src/generate/bass/index.ts +0 -350
- package/src/generate/countermelody/index.ts +0 -379
- package/src/generate/drums/beat.ts +0 -266
- package/src/generate/drums/euclid.ts +0 -58
- package/src/generate/drums/fills.ts +0 -356
- package/src/generate/drums/ghost.ts +0 -119
- package/src/generate/drums/hihat.ts +0 -285
- package/src/generate/drums/hit.ts +0 -31
- package/src/generate/drums/index.ts +0 -329
- package/src/generate/drums/internal.ts +0 -257
- package/src/generate/drums/kick.ts +0 -160
- package/src/generate/drums/percussion.ts +0 -121
- package/src/generate/drums/rng.ts +0 -7
- package/src/generate/drums/swing.ts +0 -71
- package/src/generate/groove/index.ts +0 -326
- package/src/generate/harmonize/index.ts +0 -440
- package/src/generate/index.ts +0 -59
- package/src/generate/motif/index.ts +0 -398
- package/src/generate/progression/index.ts +0 -370
- package/src/generate/reharmony/index.ts +0 -330
- package/src/generate/rhythm/index.ts +0 -165
- package/src/index.ts +0 -247
- package/src/model/chord.ts +0 -423
- package/src/model/index.ts +0 -15
- package/src/model/interval.ts +0 -82
- package/src/model/key.ts +0 -266
- package/src/model/note.ts +0 -176
- package/src/model/progression.ts +0 -161
- package/src/model/shared.ts +0 -12
- package/src/theory/chord/index.ts +0 -373
- package/src/theory/chordscale/index.ts +0 -374
- package/src/theory/counterpoint/index.ts +0 -230
- package/src/theory/harmony/index.ts +0 -92
- package/src/theory/index.ts +0 -107
- package/src/theory/safety/index.ts +0 -419
- package/src/theory/scale/degrees.ts +0 -123
- package/src/theory/scale/index.ts +0 -28
- package/src/theory/scale/key.ts +0 -56
- package/src/theory/scale/masks.ts +0 -153
- package/src/theory/spelling/index.ts +0 -182
- package/src/theory/symbol/index.ts +0 -268
- package/src/theory/voicing/index.ts +0 -5
- package/src/theory/voicing/internal.ts +0 -168
- package/src/theory/voicing/leading.ts +0 -112
- package/src/theory/voicing/satb.ts +0 -195
- package/src/theory/voicing/styled.ts +0 -176
- package/tsconfig.json +0 -23
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import type { KeyScale } from '../../core/types.js';
|
|
2
|
-
import { scaleTonesInDegreeOrder } from '../scale/index.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Chord quality identifiers understood by the chord builder.
|
|
6
|
-
*
|
|
7
|
-
* @category Chords
|
|
8
|
-
*/
|
|
9
|
-
export type ChordQuality =
|
|
10
|
-
| 'maj'
|
|
11
|
-
| 'min'
|
|
12
|
-
| 'dim'
|
|
13
|
-
| 'aug'
|
|
14
|
-
| 'maj7'
|
|
15
|
-
| 'min7'
|
|
16
|
-
| 'dom7'
|
|
17
|
-
| 'dim7'
|
|
18
|
-
| 'm7b5'
|
|
19
|
-
| 'minMaj7'
|
|
20
|
-
| 'aug7'
|
|
21
|
-
| 'augMaj7'
|
|
22
|
-
| 'majb5'
|
|
23
|
-
| '6'
|
|
24
|
-
| 'min6'
|
|
25
|
-
| '6/9'
|
|
26
|
-
| 'sus2'
|
|
27
|
-
| 'sus4'
|
|
28
|
-
| 'add9'
|
|
29
|
-
| 'add11'
|
|
30
|
-
| 'maj9'
|
|
31
|
-
| 'min9'
|
|
32
|
-
| 'dom9'
|
|
33
|
-
| '7b9'
|
|
34
|
-
| '7#9'
|
|
35
|
-
| '7#11'
|
|
36
|
-
| '7b13'
|
|
37
|
-
| '11'
|
|
38
|
-
| '13'
|
|
39
|
-
| '5';
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* A bare spelled pitch used as an enharmonic hint: a diatonic letter
|
|
43
|
-
* (0..6 = C..B) plus a chromatic alteration (-2 double-flat .. +2 double-sharp).
|
|
44
|
-
* Mirrors the letter/alter half of the pitch module's `Note` without an octave.
|
|
45
|
-
*
|
|
46
|
-
* @category Chords
|
|
47
|
-
*/
|
|
48
|
-
export type PitchSpelling = {
|
|
49
|
-
letter: number;
|
|
50
|
-
alter: number;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* A chord expressed as a root pitch class plus semitone offsets.
|
|
55
|
-
*
|
|
56
|
-
* `rootSpelling`/`bassSpelling` are optional enharmonic hints recorded by
|
|
57
|
-
* parsers (e.g. `parseChordSymbol('Bb7')`) so formatters can reproduce the
|
|
58
|
-
* original spelling instead of defaulting to sharps. Consumers may ignore
|
|
59
|
-
* them; a hint is only trusted when its pitch class still matches the
|
|
60
|
-
* corresponding `rootPc`/`bassPc`.
|
|
61
|
-
*
|
|
62
|
-
* @category Chords
|
|
63
|
-
*/
|
|
64
|
-
export type Chord = {
|
|
65
|
-
rootPc: number;
|
|
66
|
-
quality: ChordQuality;
|
|
67
|
-
intervals: number[];
|
|
68
|
-
bassPc?: number;
|
|
69
|
-
rootSpelling?: PitchSpelling;
|
|
70
|
-
bassSpelling?: PitchSpelling;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* A chord positioned in time: a root pitch class and quality placed at a beat,
|
|
75
|
-
* with optional harmonic-analysis hints. Serves as the neutral exchange record
|
|
76
|
-
* between progression generators and timeline analysers.
|
|
77
|
-
*
|
|
78
|
-
* @category Composition
|
|
79
|
-
*/
|
|
80
|
-
export type ChordSpan = {
|
|
81
|
-
rootPc: number;
|
|
82
|
-
quality: ChordQuality;
|
|
83
|
-
startBeat: number;
|
|
84
|
-
bassPc?: number;
|
|
85
|
-
/** 0-based scale degree of the chord root, when known. */
|
|
86
|
-
degree?: number;
|
|
87
|
-
/** True when the chord is a secondary dominant tonicizing another degree. */
|
|
88
|
-
secondaryDominant?: boolean;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/** Semitone offsets from the root for each supported chord quality. */
|
|
92
|
-
const QUALITY_INTERVALS: Record<ChordQuality, number[]> = {
|
|
93
|
-
maj: [0, 4, 7],
|
|
94
|
-
min: [0, 3, 7],
|
|
95
|
-
dim: [0, 3, 6],
|
|
96
|
-
aug: [0, 4, 8],
|
|
97
|
-
majb5: [0, 4, 6],
|
|
98
|
-
maj7: [0, 4, 7, 11],
|
|
99
|
-
min7: [0, 3, 7, 10],
|
|
100
|
-
dom7: [0, 4, 7, 10],
|
|
101
|
-
dim7: [0, 3, 6, 9],
|
|
102
|
-
m7b5: [0, 3, 6, 10],
|
|
103
|
-
minMaj7: [0, 3, 7, 11],
|
|
104
|
-
aug7: [0, 4, 8, 10],
|
|
105
|
-
augMaj7: [0, 4, 8, 11],
|
|
106
|
-
'6': [0, 4, 7, 9],
|
|
107
|
-
min6: [0, 3, 7, 9],
|
|
108
|
-
'6/9': [0, 4, 7, 9, 14],
|
|
109
|
-
sus2: [0, 2, 7],
|
|
110
|
-
sus4: [0, 5, 7],
|
|
111
|
-
add9: [0, 4, 7, 14],
|
|
112
|
-
add11: [0, 4, 7, 17],
|
|
113
|
-
maj9: [0, 4, 7, 11, 14],
|
|
114
|
-
min9: [0, 3, 7, 10, 14],
|
|
115
|
-
dom9: [0, 4, 7, 10, 14],
|
|
116
|
-
'7b9': [0, 4, 7, 10, 13],
|
|
117
|
-
'7#9': [0, 4, 7, 10, 15],
|
|
118
|
-
'7#11': [0, 4, 7, 10, 18],
|
|
119
|
-
'7b13': [0, 4, 7, 10, 20],
|
|
120
|
-
'11': [0, 7, 10, 14, 17],
|
|
121
|
-
'13': [0, 4, 7, 10, 14, 21],
|
|
122
|
-
'5': [0, 7],
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/** Reduce a value to a pitch class in [0, 11]. */
|
|
126
|
-
function pitchClass(value: number): number {
|
|
127
|
-
return ((Math.trunc(value) % 12) + 12) % 12;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* All chord qualities the builder understands, longest templates last.
|
|
132
|
-
*
|
|
133
|
-
* @category Chords
|
|
134
|
-
*/
|
|
135
|
-
export function chordQualities(): ChordQuality[] {
|
|
136
|
-
return Object.keys(QUALITY_INTERVALS) as ChordQuality[];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Build a chord rooted on a diatonic scale degree.
|
|
141
|
-
*
|
|
142
|
-
* The root pitch class is the degree's diatonic pitch class in `key`; the
|
|
143
|
-
* quality's interval template is attached unchanged. Degrees beyond the scale
|
|
144
|
-
* length wrap around.
|
|
145
|
-
*
|
|
146
|
-
* @param degree 0-based scale degree of the chord root.
|
|
147
|
-
* @param ext Chord quality to apply.
|
|
148
|
-
* @param key Key context supplying the diatonic root.
|
|
149
|
-
* @returns The constructed chord.
|
|
150
|
-
*
|
|
151
|
-
* @category Chords
|
|
152
|
-
*/
|
|
153
|
-
export function chordFromDegree(degree: number, ext: ChordQuality, key: KeyScale): Chord {
|
|
154
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
155
|
-
const length = tones.length;
|
|
156
|
-
const index = length > 0 ? ((degree % length) + length) % length : 0;
|
|
157
|
-
const rootPc = tones[index] ?? pitchClass(key.rootPc);
|
|
158
|
-
return { rootPc, quality: ext, intervals: [...QUALITY_INTERVALS[ext]] };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Build a chord from an explicit root pitch class and quality.
|
|
163
|
-
*
|
|
164
|
-
* @param rootPc Root pitch class (0..11).
|
|
165
|
-
* @param quality Chord quality supplying the interval template.
|
|
166
|
-
* @param bassPc Optional slash-chord bass pitch class.
|
|
167
|
-
* @returns The constructed chord.
|
|
168
|
-
*
|
|
169
|
-
* @example
|
|
170
|
-
* ```ts
|
|
171
|
-
* import { makeChord } from '@libraz/libcantus';
|
|
172
|
-
* const cmaj7 = makeChord(0, 'maj7');
|
|
173
|
-
* // { rootPc: 0, quality: 'maj7', intervals: [0, 4, 7, 11] }
|
|
174
|
-
* ```
|
|
175
|
-
*
|
|
176
|
-
* @category Chords
|
|
177
|
-
*/
|
|
178
|
-
export function makeChord(rootPc: number, quality: ChordQuality, bassPc?: number): Chord {
|
|
179
|
-
const chord: Chord = {
|
|
180
|
-
rootPc: pitchClass(rootPc),
|
|
181
|
-
quality,
|
|
182
|
-
intervals: [...QUALITY_INTERVALS[quality]],
|
|
183
|
-
};
|
|
184
|
-
if (bassPc !== undefined) {
|
|
185
|
-
chord.bassPc = pitchClass(bassPc);
|
|
186
|
-
}
|
|
187
|
-
return chord;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Get the sorted, deduplicated pitch classes of a chord.
|
|
192
|
-
*
|
|
193
|
-
* @param chord The chord to enumerate.
|
|
194
|
-
* @returns The chord's pitch classes, sorted ascending in [0, 11].
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* ```ts
|
|
198
|
-
* import { makeChord, chordPitchClasses } from '@libraz/libcantus';
|
|
199
|
-
* chordPitchClasses(makeChord(0, 'maj7')); // [0, 4, 7, 11]
|
|
200
|
-
* ```
|
|
201
|
-
*
|
|
202
|
-
* @category Chords
|
|
203
|
-
*/
|
|
204
|
-
export function chordPitchClasses(chord: Chord): number[] {
|
|
205
|
-
const set = new Set<number>();
|
|
206
|
-
for (const interval of chord.intervals) {
|
|
207
|
-
set.add(pitchClass(chord.rootPc + interval));
|
|
208
|
-
}
|
|
209
|
-
return [...set].sort((a, b) => a - b);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** The chord-tone offsets present in a chord, reduced to pitch-class space. */
|
|
213
|
-
function chordToneOffsets(chord: Chord): Set<number> {
|
|
214
|
-
return new Set(chord.intervals.map((i) => ((i % 12) + 12) % 12));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Determine a pitch's harmonic role within a chord.
|
|
219
|
-
*
|
|
220
|
-
* The role is derived from the pitch's interval above the chord root, reduced
|
|
221
|
-
* modulo 12. A major sixth (9) reads as a `sixth` for sixth chords but as a
|
|
222
|
-
* `seventh` for a diminished-seventh chord; ninths and other tensions have no
|
|
223
|
-
* basic role and return null.
|
|
224
|
-
*
|
|
225
|
-
* @param pitch MIDI pitch or bare pitch class.
|
|
226
|
-
* @param chord The chord providing the root reference.
|
|
227
|
-
* @returns The chord-tone role, or null if the pitch has no basic role.
|
|
228
|
-
*
|
|
229
|
-
* @category Chords
|
|
230
|
-
*/
|
|
231
|
-
export function chordToneRole(
|
|
232
|
-
pitch: number,
|
|
233
|
-
chord: Chord,
|
|
234
|
-
): 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | null {
|
|
235
|
-
const interval = (pitchClass(pitch) - pitchClass(chord.rootPc) + 12) % 12;
|
|
236
|
-
const tones = chordToneOffsets(chord);
|
|
237
|
-
if (interval === 0) {
|
|
238
|
-
return 'root';
|
|
239
|
-
}
|
|
240
|
-
if (interval === 3 || interval === 4) {
|
|
241
|
-
return tones.has(interval) ? 'third' : null;
|
|
242
|
-
}
|
|
243
|
-
if (interval === 7) {
|
|
244
|
-
return tones.has(7) ? 'fifth' : null;
|
|
245
|
-
}
|
|
246
|
-
if (interval === 6) {
|
|
247
|
-
// A diminished fifth is the chord's fifth only when no perfect fifth is
|
|
248
|
-
// present; alongside a perfect fifth it is a #11 tension, not a fifth.
|
|
249
|
-
return tones.has(6) && !tones.has(7) ? 'fifth' : null;
|
|
250
|
-
}
|
|
251
|
-
if (interval === 8) {
|
|
252
|
-
// An augmented fifth is the chord's fifth only without a perfect fifth;
|
|
253
|
-
// alongside a perfect fifth it is a b13 tension, not a fifth.
|
|
254
|
-
return tones.has(8) && !tones.has(7) ? 'fifth' : null;
|
|
255
|
-
}
|
|
256
|
-
if (interval === 9) {
|
|
257
|
-
const hasHigherSeventh = tones.has(10) || tones.has(11);
|
|
258
|
-
const isDiminishedSeventh = tones.has(3) && tones.has(6) && !hasHigherSeventh;
|
|
259
|
-
if (isDiminishedSeventh) {
|
|
260
|
-
return 'seventh';
|
|
261
|
-
}
|
|
262
|
-
if (tones.has(9) && !hasHigherSeventh) {
|
|
263
|
-
return 'sixth';
|
|
264
|
-
}
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
if (interval === 10 || interval === 11) {
|
|
268
|
-
return tones.has(interval) ? 'seventh' : null;
|
|
269
|
-
}
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/** Classify a stacked-thirds triad into a chord quality from its interval set. */
|
|
274
|
-
function classifyTriad(thirdIc: number, fifthIc: number): ChordQuality {
|
|
275
|
-
if (thirdIc === 4 && fifthIc === 8) {
|
|
276
|
-
return 'aug';
|
|
277
|
-
}
|
|
278
|
-
if (thirdIc === 4 && fifthIc === 6) {
|
|
279
|
-
return 'majb5';
|
|
280
|
-
}
|
|
281
|
-
if (thirdIc === 3 && fifthIc === 6) {
|
|
282
|
-
return 'dim';
|
|
283
|
-
}
|
|
284
|
-
if (thirdIc === 3) {
|
|
285
|
-
return 'min';
|
|
286
|
-
}
|
|
287
|
-
return 'maj';
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** Classify a stacked-thirds seventh chord into a chord quality. */
|
|
291
|
-
function classifySeventh(thirdIc: number, fifthIc: number, seventhIc: number): ChordQuality {
|
|
292
|
-
if (thirdIc === 3 && fifthIc === 6 && seventhIc === 9) {
|
|
293
|
-
return 'dim7';
|
|
294
|
-
}
|
|
295
|
-
if (thirdIc === 3 && fifthIc === 6 && seventhIc === 10) {
|
|
296
|
-
return 'm7b5';
|
|
297
|
-
}
|
|
298
|
-
if (thirdIc === 3 && seventhIc === 11) {
|
|
299
|
-
return 'minMaj7';
|
|
300
|
-
}
|
|
301
|
-
if (thirdIc === 3) {
|
|
302
|
-
return 'min7';
|
|
303
|
-
}
|
|
304
|
-
if (fifthIc === 8 && seventhIc === 10) {
|
|
305
|
-
return 'aug7';
|
|
306
|
-
}
|
|
307
|
-
if (fifthIc === 8 && seventhIc === 11) {
|
|
308
|
-
return 'augMaj7';
|
|
309
|
-
}
|
|
310
|
-
if (seventhIc === 11) {
|
|
311
|
-
return 'maj7';
|
|
312
|
-
}
|
|
313
|
-
return 'dom7';
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/** Stack scale thirds from a scale degree into a chord of `size` notes. */
|
|
317
|
-
function stackThirds(degree: number, key: KeyScale, size: 3 | 4): Chord {
|
|
318
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
319
|
-
const length = tones.length;
|
|
320
|
-
if (length === 0) {
|
|
321
|
-
return { rootPc: pitchClass(key.rootPc), quality: 'maj', intervals: [0, 4, 7] };
|
|
322
|
-
}
|
|
323
|
-
const idx = (step: number) => (((degree + step) % length) + length) % length;
|
|
324
|
-
const rootPc = tones[idx(0)] ?? pitchClass(key.rootPc);
|
|
325
|
-
const offsets = [0, 2, 4, 6].slice(0, size).map((step) => {
|
|
326
|
-
const pc = tones[idx(step)] ?? rootPc;
|
|
327
|
-
return (((pc - rootPc) % 12) + 12) % 12;
|
|
328
|
-
});
|
|
329
|
-
const thirdIc = offsets[1] ?? 4;
|
|
330
|
-
const fifthIc = offsets[2] ?? 7;
|
|
331
|
-
const quality =
|
|
332
|
-
size === 3
|
|
333
|
-
? classifyTriad(thirdIc, fifthIc)
|
|
334
|
-
: classifySeventh(thirdIc, fifthIc, offsets[3] ?? 11);
|
|
335
|
-
return { rootPc, quality, intervals: offsets };
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Build the diatonic triad rooted on a scale degree by stacking scale thirds.
|
|
340
|
-
*
|
|
341
|
-
* Unlike {@link chordFromDegree}, the chord quality is derived from the scale
|
|
342
|
-
* rather than supplied, so degrees yield their scale-correct triads (e.g. a
|
|
343
|
-
* diminished triad on the leading tone of a major key). Intended for heptatonic
|
|
344
|
-
* scales; other scales stack by scale step regardless.
|
|
345
|
-
*
|
|
346
|
-
* @param degree 0-based scale degree of the chord root.
|
|
347
|
-
* @param key Key/scale context.
|
|
348
|
-
* @returns The diatonic triad.
|
|
349
|
-
*
|
|
350
|
-
* @example
|
|
351
|
-
* ```ts
|
|
352
|
-
* import { majorKey, diatonicTriad } from '@libraz/libcantus';
|
|
353
|
-
* const tonic = diatonicTriad(0, majorKey(0)); // C major triad (degree 0 of C major)
|
|
354
|
-
* ```
|
|
355
|
-
*
|
|
356
|
-
* @category Chords
|
|
357
|
-
*/
|
|
358
|
-
export function diatonicTriad(degree: number, key: KeyScale): Chord {
|
|
359
|
-
return stackThirds(degree, key, 3);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Build the diatonic seventh chord rooted on a scale degree.
|
|
364
|
-
*
|
|
365
|
-
* @param degree 0-based scale degree of the chord root.
|
|
366
|
-
* @param key Key/scale context.
|
|
367
|
-
* @returns The diatonic seventh chord.
|
|
368
|
-
*
|
|
369
|
-
* @category Chords
|
|
370
|
-
*/
|
|
371
|
-
export function diatonicSeventh(degree: number, key: KeyScale): Chord {
|
|
372
|
-
return stackThirds(degree, key, 4);
|
|
373
|
-
}
|