@libraz/libcantus 0.9.4 → 0.9.5
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 -18
- package/README_ja.md +268 -0
- package/dist/analyze/index.cjs +7 -7
- package/dist/analyze/index.d.cts +9 -99
- package/dist/analyze/index.d.ts +9 -99
- package/dist/analyze/index.js +6 -6
- package/dist/{chunk-36N73KZ3.cjs → chunk-2A2QMA74.cjs} +80 -65
- package/dist/chunk-2A2QMA74.cjs.map +1 -0
- package/dist/{chunk-YLEMGEFH.js → chunk-2PTFGVD7.js} +50 -35
- package/dist/chunk-2PTFGVD7.js.map +1 -0
- package/dist/{chunk-ZEJR4TKL.cjs → chunk-357ZDPVU.cjs} +36 -47
- package/dist/chunk-357ZDPVU.cjs.map +1 -0
- package/dist/{chunk-EHVEBWM5.js → chunk-3KX76RLG.js} +136 -37
- package/dist/chunk-3KX76RLG.js.map +1 -0
- package/dist/{chunk-RC6GBMCD.cjs → chunk-4VZ5MZAM.cjs} +155 -111
- package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
- package/dist/{chunk-RK35RIN7.js → chunk-7EONYVI5.js} +115 -28
- package/dist/chunk-7EONYVI5.js.map +1 -0
- package/dist/{chunk-GQSGS2N4.js → chunk-7JEP3MFY.js} +5 -16
- package/dist/chunk-7JEP3MFY.js.map +1 -0
- package/dist/{chunk-7NKDPF74.js → chunk-AID75RN3.js} +203 -104
- package/dist/chunk-AID75RN3.js.map +1 -0
- package/dist/{chunk-FNDWXRRI.cjs → chunk-BSLTLGM3.cjs} +55 -37
- package/dist/chunk-BSLTLGM3.cjs.map +1 -0
- package/dist/{chunk-NP4NA2FB.cjs → chunk-CU6VG2YM.cjs} +39 -36
- package/dist/chunk-CU6VG2YM.cjs.map +1 -0
- package/dist/{chunk-NYYWMV2C.js → chunk-DZMHIGI4.js} +27 -17
- package/dist/chunk-DZMHIGI4.js.map +1 -0
- package/dist/{chunk-I24MV65U.js → chunk-EDOTEABK.js} +18 -15
- package/dist/{chunk-I24MV65U.js.map → chunk-EDOTEABK.js.map} +1 -1
- package/dist/{chunk-NKIBQ6PT.js → chunk-HN2OEUN2.js} +7 -4
- package/dist/chunk-HN2OEUN2.js.map +1 -0
- package/dist/{chunk-D2FAJBYL.js → chunk-KBTTEX7Q.js} +30 -12
- package/dist/chunk-KBTTEX7Q.js.map +1 -0
- package/dist/{chunk-CAN76NCU.cjs → chunk-KQN2ZYND.cjs} +147 -102
- package/dist/chunk-KQN2ZYND.cjs.map +1 -0
- package/dist/{chunk-4HLLZPXT.cjs → chunk-MPTVJIVG.cjs} +350 -251
- package/dist/chunk-MPTVJIVG.cjs.map +1 -0
- package/dist/{chunk-RUMU3UA6.cjs → chunk-MTMZTMWM.cjs} +12 -12
- package/dist/{chunk-RUMU3UA6.cjs.map → chunk-MTMZTMWM.cjs.map} +1 -1
- package/dist/{chunk-KMHOICIY.js → chunk-NNB67XA2.js} +90 -45
- package/dist/chunk-NNB67XA2.js.map +1 -0
- package/dist/{chunk-LW53AHQU.cjs → chunk-OL3IHHSJ.cjs} +228 -141
- package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
- package/dist/{chunk-7MGZ7D62.cjs → chunk-PI3UXRWK.cjs} +43 -13
- package/dist/chunk-PI3UXRWK.cjs.map +1 -0
- package/dist/{chunk-XEBG3G53.js → chunk-R2VVFAIN.js} +42 -12
- package/dist/chunk-R2VVFAIN.js.map +1 -0
- package/dist/{chunk-Q6OP4U43.cjs → chunk-WFDXOLUB.cjs} +50 -40
- package/dist/chunk-WFDXOLUB.cjs.map +1 -0
- package/dist/{chunk-NVE34YGZ.cjs → chunk-WTAN4ACG.cjs} +212 -113
- package/dist/chunk-WTAN4ACG.cjs.map +1 -0
- package/dist/{chunk-FCANXBAT.cjs → chunk-YFJXS3AU.cjs} +8 -5
- package/dist/chunk-YFJXS3AU.cjs.map +1 -0
- package/dist/{chunk-Q2XUP227.js → chunk-YUE6TJ4M.js} +89 -45
- package/dist/chunk-YUE6TJ4M.js.map +1 -0
- package/dist/{chunk-HKGYCQ34.js → chunk-ZGLJM4A4.js} +2 -2
- package/dist/core/index.cjs +15 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +14 -4
- package/dist/core/index.d.ts +14 -4
- package/dist/core/index.js +14 -4
- package/dist/{function-DNugAOrt.d.cts → function-CxixTB02.d.cts} +98 -7
- package/dist/{function-BtiZb9i9.d.ts → function-DTCt675z.d.ts} +98 -7
- package/dist/generate/index.cjs +10 -10
- package/dist/generate/index.d.cts +21 -21
- package/dist/generate/index.d.ts +21 -21
- package/dist/generate/index.js +9 -9
- package/dist/{index-tJhkmQgE.d.cts → index-B2Ulxxpo.d.cts} +3 -2
- package/dist/{index-Be74mcon.d.cts → index-BkYJ0xMq.d.cts} +2 -2
- package/dist/{index-6VR8-kit.d.ts → index-BuaB-ypt.d.ts} +3 -2
- package/dist/{index-70p168VM.d.ts → index-CAN7v-kw.d.ts} +9 -2
- package/dist/{index-C83mZI1P.d.ts → index-COw1Ky9g.d.ts} +1 -1
- package/dist/{index-tSQh-xwY.d.ts → index-Cfx8xgH0.d.ts} +1 -1
- package/dist/{index-C5UBbQ4G.d.ts → index-DRtIhlJj.d.ts} +2 -2
- package/dist/{index-BtXT6UQ0.d.cts → index-alrRninU.d.cts} +9 -2
- package/dist/{index-y_ak9_Hi.d.cts → index-oTQbYYIs.d.cts} +1 -1
- package/dist/{index-OG65ocOr.d.ts → index-tR9jTn26.d.cts} +4 -1
- package/dist/{index-OG65ocOr.d.cts → index-tR9jTn26.d.ts} +4 -1
- package/dist/{index-D6b6na69.d.cts → index-x2vcsduE.d.cts} +1 -1
- package/dist/index.cjs +24 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +23 -13
- package/dist/model/index.cjs +7 -7
- package/dist/model/index.d.cts +48 -16
- package/dist/model/index.d.ts +48 -16
- package/dist/model/index.js +6 -6
- package/dist/{styled-DIRkk9Uz.d.ts → styled-Bl0ZK28h.d.ts} +40 -3
- package/dist/{styled-DJbkJQfj.d.cts → styled-Crv6SA4S.d.cts} +40 -3
- package/dist/theory/index.cjs +6 -6
- package/dist/theory/index.d.cts +9 -9
- package/dist/theory/index.d.ts +9 -9
- package/dist/theory/index.js +5 -5
- package/package.json +12 -9
- package/dist/chunk-36N73KZ3.cjs.map +0 -1
- package/dist/chunk-4HLLZPXT.cjs.map +0 -1
- package/dist/chunk-7MGZ7D62.cjs.map +0 -1
- package/dist/chunk-7NKDPF74.js.map +0 -1
- package/dist/chunk-CAN76NCU.cjs.map +0 -1
- package/dist/chunk-D2FAJBYL.js.map +0 -1
- package/dist/chunk-EHVEBWM5.js.map +0 -1
- package/dist/chunk-FCANXBAT.cjs.map +0 -1
- package/dist/chunk-FNDWXRRI.cjs.map +0 -1
- package/dist/chunk-GQSGS2N4.js.map +0 -1
- package/dist/chunk-KMHOICIY.js.map +0 -1
- package/dist/chunk-LW53AHQU.cjs.map +0 -1
- package/dist/chunk-NKIBQ6PT.js.map +0 -1
- package/dist/chunk-NP4NA2FB.cjs.map +0 -1
- package/dist/chunk-NVE34YGZ.cjs.map +0 -1
- package/dist/chunk-NYYWMV2C.js.map +0 -1
- package/dist/chunk-Q2XUP227.js.map +0 -1
- package/dist/chunk-Q6OP4U43.cjs.map +0 -1
- package/dist/chunk-RC6GBMCD.cjs.map +0 -1
- package/dist/chunk-RK35RIN7.js.map +0 -1
- package/dist/chunk-XEBG3G53.js.map +0 -1
- package/dist/chunk-YLEMGEFH.js.map +0 -1
- package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
- /package/dist/{chunk-HKGYCQ34.js.map → chunk-ZGLJM4A4.js.map} +0 -0
|
@@ -5,35 +5,30 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunkKQN2ZYNDcjs = require('./chunk-KQN2ZYND.cjs');
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
var
|
|
14
|
+
var _chunkBSLTLGM3cjs = require('./chunk-BSLTLGM3.cjs');
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
var
|
|
17
|
+
var _chunkPI3UXRWKcjs = require('./chunk-PI3UXRWK.cjs');
|
|
18
18
|
|
|
19
19
|
// src/generate/reharmony/index.ts
|
|
20
20
|
function hasInterval(chord, semitone) {
|
|
21
|
-
return chord.intervals.some((iv) =>
|
|
21
|
+
return chord.intervals.some((iv) => _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, iv) === semitone);
|
|
22
22
|
}
|
|
23
23
|
function isDominantType(chord) {
|
|
24
24
|
return hasInterval(chord, 4) && hasInterval(chord, 10);
|
|
25
25
|
}
|
|
26
|
-
function isStableTarget(chord) {
|
|
27
|
-
const hasFifth = hasInterval(chord, 7);
|
|
28
|
-
const hasThird = hasInterval(chord, 3) || hasInterval(chord, 4);
|
|
29
|
-
return hasFifth && hasThird && !isDominantType(chord);
|
|
30
|
-
}
|
|
31
26
|
function commonToneCount(a, b) {
|
|
32
27
|
const set = new Set(b);
|
|
33
28
|
return a.filter((pc) => set.has(pc)).length;
|
|
34
29
|
}
|
|
35
30
|
function triadQualityOf(chord) {
|
|
36
|
-
const semis = new Set(chord.intervals.map((interval) =>
|
|
31
|
+
const semis = new Set(chord.intervals.map((interval) => _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, interval)));
|
|
37
32
|
const hasThird = semis.has(4) ? "maj" : semis.has(3) ? "min" : void 0;
|
|
38
33
|
if (hasThird === void 0) {
|
|
39
34
|
return chord.quality;
|
|
@@ -47,63 +42,57 @@ function triadQualityOf(chord) {
|
|
|
47
42
|
return hasThird;
|
|
48
43
|
}
|
|
49
44
|
function diatonicTriadsOf(key) {
|
|
50
|
-
const degrees =
|
|
45
|
+
const degrees = _chunkBSLTLGM3cjs.scaleTonesInDegreeOrder.call(void 0, key).length;
|
|
51
46
|
const triads = [];
|
|
52
47
|
for (let degree = 0; degree < degrees; degree += 1) {
|
|
53
|
-
triads.push(
|
|
48
|
+
triads.push(_chunkBSLTLGM3cjs.diatonicTriad.call(void 0, degree, key));
|
|
54
49
|
}
|
|
55
50
|
return triads;
|
|
56
51
|
}
|
|
57
52
|
function sameChord(a, b) {
|
|
58
|
-
return
|
|
53
|
+
return _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, a.rootPc) === _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, b.rootPc) && a.quality === b.quality;
|
|
59
54
|
}
|
|
60
55
|
var THIRD_OFFSETS = [3, 4, 8, 9];
|
|
61
56
|
function substituteChord(chord, key, opts) {
|
|
62
|
-
const originalTriad =
|
|
57
|
+
const originalTriad = _chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, _chunkBSLTLGM3cjs.makeChord.call(void 0, chord.rootPc, triadQualityOf(chord)));
|
|
63
58
|
const candidates = [];
|
|
64
59
|
if (isDominantType(chord)) {
|
|
65
|
-
candidates.push({ chord:
|
|
66
|
-
}
|
|
67
|
-
if (isStableTarget(chord)) {
|
|
68
|
-
candidates.push({
|
|
69
|
-
chord: _chunkFNDWXRRIcjs.makeChord.call(void 0, _chunk7MGZ7D62cjs.pitchClassOf.call(void 0, chord.rootPc + 7), "dom7"),
|
|
70
|
-
type: "secondaryDominant"
|
|
71
|
-
});
|
|
60
|
+
candidates.push({ chord: _chunkBSLTLGM3cjs.makeChord.call(void 0, _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.rootPc + 6), "dom7"), type: "tritone" });
|
|
72
61
|
}
|
|
73
62
|
for (const triad of diatonicTriadsOf(key)) {
|
|
74
|
-
const offset =
|
|
75
|
-
if (THIRD_OFFSETS.includes(offset) && commonToneCount(originalTriad,
|
|
63
|
+
const offset = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, triad.rootPc - chord.rootPc);
|
|
64
|
+
if (THIRD_OFFSETS.includes(offset) && commonToneCount(originalTriad, _chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, triad)) === 2) {
|
|
76
65
|
candidates.push({ chord: triad, type: "relative" });
|
|
77
66
|
}
|
|
78
67
|
}
|
|
79
|
-
const targetFunction =
|
|
80
|
-
for (const triad of diatonicTriadsOf(
|
|
81
|
-
if (
|
|
68
|
+
const targetFunction = _chunkKQN2ZYNDcjs.functionOf.call(void 0, chord, key);
|
|
69
|
+
for (const triad of diatonicTriadsOf(_chunkKQN2ZYNDcjs.parallelKey.call(void 0, key))) {
|
|
70
|
+
if (_chunkKQN2ZYNDcjs.functionOf.call(void 0, triad, key) === targetFunction && !_chunkKQN2ZYNDcjs.isDiatonic.call(void 0, triad, key)) {
|
|
82
71
|
candidates.push({ chord: triad, type: "borrowed" });
|
|
83
72
|
}
|
|
84
73
|
}
|
|
85
74
|
for (const offset of THIRD_OFFSETS) {
|
|
86
75
|
for (const quality of ["maj", "min"]) {
|
|
87
|
-
const mediant =
|
|
88
|
-
if (commonToneCount(originalTriad,
|
|
76
|
+
const mediant = _chunkBSLTLGM3cjs.makeChord.call(void 0, _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.rootPc + offset), quality);
|
|
77
|
+
if (commonToneCount(originalTriad, _chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, mediant)) === 1 && !_chunkKQN2ZYNDcjs.isDiatonic.call(void 0, mediant, key)) {
|
|
89
78
|
candidates.push({ chord: mediant, type: "chromaticMediant" });
|
|
90
79
|
}
|
|
91
80
|
}
|
|
92
81
|
}
|
|
93
|
-
const melodyPcs = _optionalChain([opts, 'optionalAccess', _ => _.melodyPcs, 'optionalAccess', _2 => _2.map, 'call', _3 => _3(
|
|
82
|
+
const melodyPcs = _optionalChain([opts, 'optionalAccess', _ => _.melodyPcs, 'optionalAccess', _2 => _2.map, 'call', _3 => _3(_chunkPI3UXRWKcjs.pitchClassOf)]);
|
|
94
83
|
const seen = /* @__PURE__ */ new Set();
|
|
95
84
|
const results = [];
|
|
96
85
|
for (const candidate of candidates) {
|
|
97
86
|
if (sameChord(candidate.chord, chord)) {
|
|
98
87
|
continue;
|
|
99
88
|
}
|
|
100
|
-
const dedupeKey = `${
|
|
89
|
+
const dedupeKey = `${_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, candidate.chord.rootPc)}:${candidate.chord.quality}`;
|
|
101
90
|
if (seen.has(dedupeKey)) {
|
|
102
91
|
continue;
|
|
103
92
|
}
|
|
104
93
|
seen.add(dedupeKey);
|
|
105
94
|
if (melodyPcs !== void 0) {
|
|
106
|
-
const pcs = new Set(
|
|
95
|
+
const pcs = new Set(_chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, candidate.chord));
|
|
107
96
|
if (!melodyPcs.every((pc) => pcs.has(pc))) {
|
|
108
97
|
continue;
|
|
109
98
|
}
|
|
@@ -114,10 +103,10 @@ function substituteChord(chord, key, opts) {
|
|
|
114
103
|
// A secondary dominant is named as the applied chord it is, so the
|
|
115
104
|
// numeral re-parses to the chord that was proposed rather than to a
|
|
116
105
|
// chromatic chord on the same root.
|
|
117
|
-
roman:
|
|
106
|
+
roman: _chunkKQN2ZYNDcjs.chordToRoman.call(void 0, candidate.chord, key, {
|
|
118
107
|
applied: candidate.type === "secondaryDominant"
|
|
119
108
|
}),
|
|
120
|
-
function:
|
|
109
|
+
function: _chunkKQN2ZYNDcjs.functionOf.call(void 0, candidate.chord, key)
|
|
121
110
|
});
|
|
122
111
|
}
|
|
123
112
|
return results;
|
|
@@ -126,43 +115,43 @@ function modalInterchangePalette(key) {
|
|
|
126
115
|
const palette = [];
|
|
127
116
|
const seen = /* @__PURE__ */ new Set();
|
|
128
117
|
const add = (chord) => {
|
|
129
|
-
const dedupeKey = `${
|
|
118
|
+
const dedupeKey = `${_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.rootPc)}:${chord.quality}`;
|
|
130
119
|
if (seen.has(dedupeKey)) {
|
|
131
120
|
return;
|
|
132
121
|
}
|
|
133
122
|
seen.add(dedupeKey);
|
|
134
123
|
palette.push({
|
|
135
124
|
chord,
|
|
136
|
-
roman:
|
|
137
|
-
source:
|
|
125
|
+
roman: _chunkKQN2ZYNDcjs.chordToRoman.call(void 0, chord, key),
|
|
126
|
+
source: _chunkKQN2ZYNDcjs.borrowedSource.call(void 0, chord, key)
|
|
138
127
|
});
|
|
139
128
|
};
|
|
140
|
-
for (const triad of diatonicTriadsOf(
|
|
141
|
-
if (!
|
|
129
|
+
for (const triad of diatonicTriadsOf(_chunkKQN2ZYNDcjs.parallelKey.call(void 0, key))) {
|
|
130
|
+
if (!_chunkKQN2ZYNDcjs.isDiatonic.call(void 0, triad, key) && _chunkKQN2ZYNDcjs.borrowedSource.call(void 0, triad, key) !== null) {
|
|
142
131
|
add(triad);
|
|
143
132
|
}
|
|
144
133
|
}
|
|
145
|
-
const neapolitan =
|
|
146
|
-
if (!
|
|
134
|
+
const neapolitan = _chunkBSLTLGM3cjs.makeChord.call(void 0, _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, key.rootPc + 1), "maj");
|
|
135
|
+
if (!_chunkKQN2ZYNDcjs.isDiatonic.call(void 0, neapolitan, key)) {
|
|
147
136
|
add(neapolitan);
|
|
148
137
|
}
|
|
149
138
|
return palette;
|
|
150
139
|
}
|
|
151
140
|
function negativeHarmonyMirror(chord, key) {
|
|
152
|
-
const tonic =
|
|
153
|
-
const mirror = (p) =>
|
|
154
|
-
const pcs =
|
|
141
|
+
const tonic = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, key.rootPc);
|
|
142
|
+
const mirror = (p) => _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, 2 * tonic + 7 - p);
|
|
143
|
+
const pcs = _chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, chord).map(mirror);
|
|
155
144
|
const bassPc = chord.bassPc !== void 0 ? mirror(chord.bassPc) : void 0;
|
|
156
145
|
const pitches = bassPc !== void 0 ? [bassPc - 12, ...pcs] : pcs;
|
|
157
|
-
const detected =
|
|
146
|
+
const detected = _chunkKQN2ZYNDcjs.detectChordBest.call(void 0, pitches);
|
|
158
147
|
if (detected) {
|
|
159
148
|
if (bassPc === void 0 && detected.bassPc !== void 0) {
|
|
160
|
-
return
|
|
149
|
+
return _chunkBSLTLGM3cjs.makeChord.call(void 0, detected.rootPc, detected.quality);
|
|
161
150
|
}
|
|
162
151
|
return detected;
|
|
163
152
|
}
|
|
164
153
|
const quality = chord.quality;
|
|
165
|
-
return
|
|
154
|
+
return _chunkBSLTLGM3cjs.makeChord.call(void 0, mirror(chord.rootPc), quality, bassPc);
|
|
166
155
|
}
|
|
167
156
|
|
|
168
157
|
|
|
@@ -170,4 +159,4 @@ function negativeHarmonyMirror(chord, key) {
|
|
|
170
159
|
|
|
171
160
|
|
|
172
161
|
exports.substituteChord = substituteChord; exports.modalInterchangePalette = modalInterchangePalette; exports.negativeHarmonyMirror = negativeHarmonyMirror;
|
|
173
|
-
//# sourceMappingURL=chunk-
|
|
162
|
+
//# sourceMappingURL=chunk-357ZDPVU.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-357ZDPVU.cjs","../src/generate/reharmony/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-357ZDPVU.cjs","sourcesContent":[null,"/**\n * Reharmonization: chord substitution, modal-interchange palettes, and negative\n * harmony. These transforms take an existing chord (or a key) and propose\n * alternative harmonies that preserve a chosen relationship — a shared function,\n * a common tone, a tonicizing dominant, or a reflection across the key axis.\n *\n * Roots are pitch classes (0..11); no spelled key signature is required.\n */\n\nimport { detectChordBest } from '../../analyze/detect/index.js';\nimport {\n type BorrowedSource,\n borrowedSource,\n chordToRoman,\n functionOf,\n type HarmonicFunction,\n isDiatonic,\n parallelKey,\n} from '../../analyze/functional/index.js';\nimport { pitchClassOf as mod12 } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n type Chord,\n type ChordQuality,\n chordPitchClasses,\n diatonicTriad,\n makeChord,\n} from '../../theory/chord/index.js';\nimport { scaleTonesInDegreeOrder } from '../../theory/scale/index.js';\n\n/**\n * The kind of substitution relationship a candidate realizes.\n *\n * @category Reharmonization\n */\nexport type SubstitutionType =\n | 'tritone'\n | 'relative'\n | 'borrowed'\n | 'chromaticMediant'\n | 'secondaryDominant';\n\n/**\n * A proposed chord substitution with its relationship, numeral, and function.\n *\n * @category Reharmonization\n */\nexport type Substitution = {\n chord: Chord;\n type: SubstitutionType;\n roman: string;\n function: HarmonicFunction;\n};\n\n/**\n * Options controlling which substitutions {@link substituteChord} returns.\n *\n * @category Reharmonization\n */\nexport type SubstituteOptions = {\n /**\n * Melody pitch classes that must remain chord tones. When given, only\n * substitutions whose pitch classes contain every listed pitch class are\n * kept, so the melody stays consonant against the new harmony.\n */\n melodyPcs?: number[];\n};\n\n/** Whether the chord's template carries a given interval (mod 12). */\nfunction hasInterval(chord: Chord, semitone: number): boolean {\n return chord.intervals.some((iv) => mod12(iv) === semitone);\n}\n\n/** Whether a chord is a dominant type: a major third with a minor seventh. */\nfunction isDominantType(chord: Chord): boolean {\n return hasInterval(chord, 4) && hasInterval(chord, 10);\n}\n\n/** Count the pitch classes shared between two pitch-class sets. */\nfunction commonToneCount(a: number[], b: number[]): number {\n const set = new Set(b);\n return a.filter((pc) => set.has(pc)).length;\n}\n\n/** The plain triad quality underlying a chord's own quality. */\nfunction triadQualityOf(chord: Chord): ChordQuality {\n const semis = new Set(chord.intervals.map((interval) => mod12(interval)));\n const hasThird = semis.has(4) ? 'maj' : semis.has(3) ? 'min' : undefined;\n if (hasThird === undefined) {\n return chord.quality; // sus, power, and other non-tertian chords stand alone\n }\n if (semis.has(6) && !semis.has(7)) {\n return hasThird === 'min' ? 'dim' : 'majb5';\n }\n if (semis.has(8) && !semis.has(7)) {\n return 'aug';\n }\n return hasThird;\n}\n\n/** The diatonic triads of a key, one per scale degree. */\nfunction diatonicTriadsOf(key: KeyScale): Chord[] {\n const degrees = scaleTonesInDegreeOrder(key).length;\n const triads: Chord[] = [];\n for (let degree = 0; degree < degrees; degree += 1) {\n triads.push(diatonicTriad(degree, key));\n }\n return triads;\n}\n\n/** Whether two chords share the same root pitch class and quality. */\nfunction sameChord(a: Chord, b: Chord): boolean {\n return mod12(a.rootPc) === mod12(b.rootPc) && a.quality === b.quality;\n}\n\n/** Root offsets (semitones) that form a third above or below a root. */\nconst THIRD_OFFSETS = [3, 4, 8, 9] as const;\n\n/**\n * Propose substitutions for a chord within a key.\n *\n * Candidates are generated by several relationships and deduplicated against the\n * original chord and each other:\n *\n * - `tritone`: for a dominant-type chord, the dominant seventh a tritone away.\n * - `relative`: a diatonic triad a third away that shares two common tones.\n * - `borrowed`: a triad from the parallel mode with the same harmonic function.\n * - `chromaticMediant`: a major/minor triad a third away sharing one common\n * tone.\n *\n * Each result carries its Roman numeral and harmonic function in `key`. When\n * `opts.melodyPcs` is given, only substitutions whose pitch classes contain all\n * of those pitch classes are returned.\n *\n * @param chord The chord to reharmonize.\n * @param key The prevailing key.\n * @param opts Optional melody-preservation constraint.\n * @returns The deduplicated substitution candidates.\n * @example\n * ```ts\n * import { substituteChord, makeChord, majorKey } from '@libraz/libcantus';\n * const subs = substituteChord(makeChord(7, 'dom7'), majorKey(0));\n * // includes the tritone sub Db7 (rootPc 1, quality 'dom7')\n * ```\n * @category Reharmonization\n */\nexport function substituteChord(\n chord: Chord,\n key: KeyScale,\n opts?: SubstituteOptions,\n): Substitution[] {\n // The relative test counts common tones against the chord's triad, not its\n // full pitch-class set: an exact count of two would otherwise be decided by\n // how many tensions the input carries, so C proposes both Em and Am while\n // Cmaj7 proposes only Am — penalising the candidate that overlaps *more*.\n const originalTriad = chordPitchClasses(makeChord(chord.rootPc, triadQualityOf(chord)));\n const candidates: { chord: Chord; type: SubstitutionType }[] = [];\n\n // Tritone substitution: only for dominant-type chords.\n if (isDominantType(chord)) {\n candidates.push({ chord: makeChord(mod12(chord.rootPc + 6), 'dom7'), type: 'tritone' });\n }\n\n // Relative: a diatonic triad a third away sharing two common tones.\n for (const triad of diatonicTriadsOf(key)) {\n const offset = mod12(triad.rootPc - chord.rootPc);\n if (\n (THIRD_OFFSETS as readonly number[]).includes(offset) &&\n commonToneCount(originalTriad, chordPitchClasses(triad)) === 2\n ) {\n candidates.push({ chord: triad, type: 'relative' });\n }\n }\n\n // Borrowed: a parallel-mode triad sharing the original's harmonic function.\n const targetFunction = functionOf(chord, key);\n for (const triad of diatonicTriadsOf(parallelKey(key))) {\n if (functionOf(triad, key) === targetFunction && !isDiatonic(triad, key)) {\n candidates.push({ chord: triad, type: 'borrowed' });\n }\n }\n\n // Chromatic mediants: major/minor triads a third away sharing one common tone.\n for (const offset of THIRD_OFFSETS) {\n for (const quality of ['maj', 'min'] as const) {\n const mediant = makeChord(mod12(chord.rootPc + offset), quality);\n if (\n commonToneCount(originalTriad, chordPitchClasses(mediant)) === 1 &&\n !isDiatonic(mediant, key)\n ) {\n candidates.push({ chord: mediant, type: 'chromaticMediant' });\n }\n }\n }\n\n const melodyPcs = opts?.melodyPcs?.map(mod12);\n const seen = new Set<string>();\n const results: Substitution[] = [];\n for (const candidate of candidates) {\n if (sameChord(candidate.chord, chord)) {\n continue;\n }\n const dedupeKey = `${mod12(candidate.chord.rootPc)}:${candidate.chord.quality}`;\n if (seen.has(dedupeKey)) {\n continue;\n }\n seen.add(dedupeKey);\n if (melodyPcs !== undefined) {\n const pcs = new Set(chordPitchClasses(candidate.chord));\n if (!melodyPcs.every((pc) => pcs.has(pc))) {\n continue;\n }\n }\n results.push({\n chord: candidate.chord,\n type: candidate.type,\n // A secondary dominant is named as the applied chord it is, so the\n // numeral re-parses to the chord that was proposed rather than to a\n // chromatic chord on the same root.\n roman: chordToRoman(candidate.chord, key, {\n applied: candidate.type === 'secondaryDominant',\n }),\n function: functionOf(candidate.chord, key),\n });\n }\n return results;\n}\n\n/**\n * A borrowed chord in the modal-interchange palette of a key.\n *\n * @category Reharmonization\n */\nexport type BorrowedChord = {\n chord: Chord;\n roman: string;\n source: BorrowedSource;\n};\n\n/**\n * The modal-interchange palette of a key.\n *\n * Lists the diatonic triads of the {@link parallelKey} that are not diatonic to\n * `key` (the classic borrowed chords such as `iv`, `bVI`, and `bVII` in a major\n * key), plus the Neapolitan (a major triad on the flat second degree). Each\n * carries its Roman numeral and {@link borrowedSource} relative to `key`.\n *\n * @param key The prevailing key.\n * @returns The borrowed-chord palette.\n * @category Reharmonization\n */\nexport function modalInterchangePalette(key: KeyScale): BorrowedChord[] {\n const palette: BorrowedChord[] = [];\n const seen = new Set<string>();\n const add = (chord: Chord) => {\n const dedupeKey = `${mod12(chord.rootPc)}:${chord.quality}`;\n if (seen.has(dedupeKey)) {\n return;\n }\n seen.add(dedupeKey);\n palette.push({\n chord,\n roman: chordToRoman(chord, key),\n source: borrowedSource(chord, key),\n });\n };\n\n for (const triad of diatonicTriadsOf(parallelKey(key))) {\n if (!isDiatonic(triad, key) && borrowedSource(triad, key) !== null) {\n add(triad);\n }\n }\n // The Neapolitan (major triad on b2) sits outside both parallel modes, so it\n // is added explicitly — unless b2 is already diatonic (e.g. in a Phrygian key,\n // where it is a native chord rather than a borrowing).\n const neapolitan = makeChord(mod12(key.rootPc + 1), 'maj');\n if (!isDiatonic(neapolitan, key)) {\n add(neapolitan);\n }\n return palette;\n}\n\n/**\n * Reflect a chord across the tonic–dominant axis of a key (negative harmony).\n *\n * Every pitch class `p` maps to `(2*T + 7 - p) mod 12`, where `T` is the key\n * tonic — the reflection that swaps the tonic with the dominant. The mirrored\n * pitch classes (including the bass, if set) are reassembled into a chord by\n * {@link detectChordBest}; when no chord is recognized, the mirrored root and\n * the original quality are used as a fallback.\n *\n * @param chord The chord to reflect.\n * @param key The prevailing key.\n * @returns The negative-harmony counterpart of the chord.\n * @example\n * ```ts\n * import { negativeHarmonyMirror, makeChord, majorKey } from '@libraz/libcantus';\n * negativeHarmonyMirror(makeChord(7, 'maj'), majorKey(0));\n * // G major reflected across C's axis => F minor (rootPc 5, quality 'min')\n * ```\n * @category Reharmonization\n */\nexport function negativeHarmonyMirror(chord: Chord, key: KeyScale): Chord {\n const tonic = mod12(key.rootPc);\n const mirror = (p: number) => mod12(2 * tonic + 7 - p);\n const pcs = chordPitchClasses(chord).map(mirror);\n const bassPc = chord.bassPc !== undefined ? mirror(chord.bassPc) : undefined;\n // Place the mirrored bass an octave below so it is recognized as the lowest\n // note and preserved through detection.\n const pitches = bassPc !== undefined ? [bassPc - 12, ...pcs] : pcs;\n const detected = detectChordBest(pitches);\n if (detected) {\n // Without an input bass the mirrored tones are octave-less, so detection may\n // read the lowest pitch class as a slash bass and report a spurious\n // inversion. Restore the recognized chord to root position in that case.\n if (bassPc === undefined && detected.bassPc !== undefined) {\n return makeChord(detected.rootPc, detected.quality);\n }\n return detected;\n }\n const quality: ChordQuality = chord.quality;\n return makeChord(mirror(chord.rootPc), quality, bassPc);\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
negativeHarmonyMirror
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7JEP3MFY.js";
|
|
4
4
|
import {
|
|
5
5
|
analyzeChord,
|
|
6
6
|
borrowedSource,
|
|
@@ -14,13 +14,14 @@ import {
|
|
|
14
14
|
isBorrowedChord,
|
|
15
15
|
isMinorKey,
|
|
16
16
|
romanToChord
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-NNB67XA2.js";
|
|
18
18
|
import {
|
|
19
19
|
availableTensions,
|
|
20
20
|
avoidNotes,
|
|
21
21
|
chordScales,
|
|
22
22
|
formatChordSymbol,
|
|
23
23
|
parseChordSymbol,
|
|
24
|
+
scalesForChanges,
|
|
24
25
|
spellChord,
|
|
25
26
|
spellChordFromRoot,
|
|
26
27
|
spellPitchClass,
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
voiceChord,
|
|
29
30
|
voiceChordStyled,
|
|
30
31
|
voiceProgression
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-7EONYVI5.js";
|
|
32
33
|
import {
|
|
33
34
|
chordFromDegree,
|
|
34
35
|
chordPitchClasses,
|
|
@@ -41,10 +42,11 @@ import {
|
|
|
41
42
|
scaleByName,
|
|
42
43
|
scaleTonesInDegreeOrder,
|
|
43
44
|
transposeChord
|
|
44
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-KBTTEX7Q.js";
|
|
45
46
|
import {
|
|
46
47
|
InvalidInputError,
|
|
47
48
|
assertFiniteNumber,
|
|
49
|
+
assertInteger,
|
|
48
50
|
formatNote,
|
|
49
51
|
intervalSemitones,
|
|
50
52
|
isConsonantInterval,
|
|
@@ -57,7 +59,7 @@ import {
|
|
|
57
59
|
spelledInterval,
|
|
58
60
|
transposeByInterval,
|
|
59
61
|
transposeNote
|
|
60
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-R2VVFAIN.js";
|
|
61
63
|
|
|
62
64
|
// src/model/interval.ts
|
|
63
65
|
var INVERSION_SUM = 9;
|
|
@@ -77,10 +79,12 @@ var Interval = class _Interval {
|
|
|
77
79
|
#number;
|
|
78
80
|
#quality;
|
|
79
81
|
#semitones;
|
|
80
|
-
|
|
82
|
+
#descending;
|
|
83
|
+
constructor(numberValue, quality, semitones, descending = semitones < 0) {
|
|
81
84
|
this.#number = numberValue;
|
|
82
85
|
this.#quality = quality;
|
|
83
86
|
this.#semitones = semitones;
|
|
87
|
+
this.#descending = descending;
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
86
90
|
* The spelled interval between two notes.
|
|
@@ -119,7 +123,12 @@ var Interval = class _Interval {
|
|
|
119
123
|
* @returns The wrapped interval.
|
|
120
124
|
*/
|
|
121
125
|
static fromData(data) {
|
|
122
|
-
return new _Interval(
|
|
126
|
+
return new _Interval(
|
|
127
|
+
data.number,
|
|
128
|
+
data.quality,
|
|
129
|
+
data.semitones,
|
|
130
|
+
data.descending ?? data.semitones < 0
|
|
131
|
+
);
|
|
123
132
|
}
|
|
124
133
|
/**
|
|
125
134
|
* Rebuild an interval from its {@link Interval.toJSON} output.
|
|
@@ -166,7 +175,7 @@ var Interval = class _Interval {
|
|
|
166
175
|
* @returns The inverted interval, e.g. `M3` becomes `m6`.
|
|
167
176
|
*/
|
|
168
177
|
invert() {
|
|
169
|
-
const simple = (this.#number - 1) % 7 + 1;
|
|
178
|
+
const simple = this.#number === 8 ? 8 : (this.#number - 1) % 7 + 1;
|
|
170
179
|
const numberValue = INVERSION_SUM - simple;
|
|
171
180
|
const quality = invertQuality(this.#quality);
|
|
172
181
|
return _Interval.of(numberValue, quality, intervalSemitones(numberValue, quality));
|
|
@@ -191,7 +200,7 @@ var Interval = class _Interval {
|
|
|
191
200
|
* @returns True when number, quality, and span all match.
|
|
192
201
|
*/
|
|
193
202
|
equals(other) {
|
|
194
|
-
return this.#number === other.number && this.#quality === other.quality && this.#semitones === other.semitones;
|
|
203
|
+
return this.#number === other.number && this.#quality === other.quality && this.#semitones === other.semitones && this.#descending === other.#descending;
|
|
195
204
|
}
|
|
196
205
|
/**
|
|
197
206
|
* The plain interval data, for JSON serialization.
|
|
@@ -202,7 +211,17 @@ var Interval = class _Interval {
|
|
|
202
211
|
* @returns The diatonic number, quality, and semitone span.
|
|
203
212
|
*/
|
|
204
213
|
toJSON() {
|
|
205
|
-
return
|
|
214
|
+
return this.#descending ? {
|
|
215
|
+
number: this.#number,
|
|
216
|
+
quality: this.#quality,
|
|
217
|
+
semitones: this.#semitones,
|
|
218
|
+
descending: true
|
|
219
|
+
} : this.#semitones < 0 ? {
|
|
220
|
+
number: this.#number,
|
|
221
|
+
quality: this.#quality,
|
|
222
|
+
semitones: this.#semitones,
|
|
223
|
+
descending: false
|
|
224
|
+
} : { number: this.#number, quality: this.#quality, semitones: this.#semitones };
|
|
206
225
|
}
|
|
207
226
|
/**
|
|
208
227
|
* The interval's name, so a template literal or a log line reads as the
|
|
@@ -226,6 +245,7 @@ function copyNote(data) {
|
|
|
226
245
|
if (data.octave !== void 0) {
|
|
227
246
|
copy.octave = data.octave;
|
|
228
247
|
}
|
|
248
|
+
noteToPitchClass(copy);
|
|
229
249
|
return copy;
|
|
230
250
|
}
|
|
231
251
|
var Note = class _Note {
|
|
@@ -419,13 +439,20 @@ function bestTonicForScale(rootPc, scale) {
|
|
|
419
439
|
var Key = class _Key {
|
|
420
440
|
#scale;
|
|
421
441
|
#tonic;
|
|
442
|
+
#variant;
|
|
422
443
|
/**
|
|
423
444
|
* Wrap a key/scale and its spelled tonic.
|
|
424
445
|
*
|
|
425
446
|
* @param scale The key/scale; its root is normalized to a pitch class.
|
|
426
447
|
* @param tonic The spelled tonic anchoring letter-name spelling.
|
|
448
|
+
* @param variant Optional detected scale form retained for display.
|
|
427
449
|
*/
|
|
428
|
-
constructor(scale, tonic) {
|
|
450
|
+
constructor(scale, tonic, variant) {
|
|
451
|
+
assertInteger(scale.rootPc, "scale.rootPc");
|
|
452
|
+
assertInteger(scale.modeMask12, "scale.modeMask12", 1, 4095);
|
|
453
|
+
if ((scale.modeMask12 & 1) === 0) {
|
|
454
|
+
throw new InvalidInputError("scale.modeMask12 must include the tonic (bit 0)");
|
|
455
|
+
}
|
|
429
456
|
const rootPc = pitchClassOf(scale.rootPc);
|
|
430
457
|
if (tonic.pitchClass !== rootPc) {
|
|
431
458
|
throw new InvalidInputError(
|
|
@@ -434,6 +461,16 @@ var Key = class _Key {
|
|
|
434
461
|
}
|
|
435
462
|
this.#scale = { rootPc, modeMask12: scale.modeMask12 };
|
|
436
463
|
this.#tonic = tonic;
|
|
464
|
+
this.#variant = variant;
|
|
465
|
+
}
|
|
466
|
+
/** Preserve detection metadata while replacing its plain key with this API's Key. */
|
|
467
|
+
static #fromMatch(match) {
|
|
468
|
+
const key = new _Key(
|
|
469
|
+
match.key,
|
|
470
|
+
bestTonicForScale(pitchClassOf(match.key.rootPc), match.key),
|
|
471
|
+
match.variant
|
|
472
|
+
);
|
|
473
|
+
return { ...match, key };
|
|
437
474
|
}
|
|
438
475
|
/**
|
|
439
476
|
* A major key.
|
|
@@ -521,7 +558,17 @@ var Key = class _Key {
|
|
|
521
558
|
* @returns Ranked keys (may be empty).
|
|
522
559
|
*/
|
|
523
560
|
static detect(pitches, opts) {
|
|
524
|
-
return
|
|
561
|
+
return _Key.detectMatches(pitches, opts).map((match) => match.key);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Identify keys while retaining each candidate's score, fit, and scale form.
|
|
565
|
+
*
|
|
566
|
+
* @param pitches MIDI pitches or bare pitch classes.
|
|
567
|
+
* @param opts How to weigh the input; see {@link DetectKeyOptions}.
|
|
568
|
+
* @returns Ranked key matches (may be empty).
|
|
569
|
+
*/
|
|
570
|
+
static detectMatches(pitches, opts) {
|
|
571
|
+
return detectKey(pitches, opts).map((match) => _Key.#fromMatch(match));
|
|
525
572
|
}
|
|
526
573
|
/**
|
|
527
574
|
* The single best key interpretation of a pitch set.
|
|
@@ -537,7 +584,7 @@ var Key = class _Key {
|
|
|
537
584
|
*/
|
|
538
585
|
static detectBest(pitches, opts) {
|
|
539
586
|
const best = detectKeyBest(pitches, opts);
|
|
540
|
-
return best === null ? null : _Key
|
|
587
|
+
return best === null ? null : _Key.#fromMatch(best).key;
|
|
541
588
|
}
|
|
542
589
|
/**
|
|
543
590
|
* Whether another key has the same tonic pitch class and mode.
|
|
@@ -568,6 +615,10 @@ var Key = class _Key {
|
|
|
568
615
|
get isMinor() {
|
|
569
616
|
return isMinorKey(this.#scale);
|
|
570
617
|
}
|
|
618
|
+
/** The detected scale form, when this key came from key detection. */
|
|
619
|
+
get variant() {
|
|
620
|
+
return this.#variant;
|
|
621
|
+
}
|
|
571
622
|
/**
|
|
572
623
|
* The scale's pitch classes in ascending scale-degree order (degree 0 first).
|
|
573
624
|
*
|
|
@@ -594,7 +645,11 @@ var Key = class _Key {
|
|
|
594
645
|
transpose(semitones) {
|
|
595
646
|
assertFiniteNumber(semitones, "semitones");
|
|
596
647
|
const rootPc = pitchClassOf(this.#scale.rootPc + Math.round(semitones));
|
|
597
|
-
return _Key
|
|
648
|
+
return new _Key(
|
|
649
|
+
{ rootPc, modeMask12: this.#scale.modeMask12 },
|
|
650
|
+
this.#tonic.transpose(semitones),
|
|
651
|
+
this.#variant
|
|
652
|
+
);
|
|
598
653
|
}
|
|
599
654
|
/**
|
|
600
655
|
* The spelled scale, one note per degree (e.g. C D E F G A B for C major).
|
|
@@ -696,12 +751,14 @@ var Key = class _Key {
|
|
|
696
751
|
}
|
|
697
752
|
/**
|
|
698
753
|
* The key's tonic and mode, so a template literal or a log line reads as the
|
|
699
|
-
* key.
|
|
700
|
-
* a scale name.
|
|
754
|
+
* key. Detected harmonic and melodic minor keys retain their scale form.
|
|
701
755
|
*
|
|
702
756
|
* @returns The name, e.g. `'C major'` or `'A minor'`.
|
|
703
757
|
*/
|
|
704
758
|
toString() {
|
|
759
|
+
if (this.#variant === "harmonic" || this.#variant === "melodic") {
|
|
760
|
+
return `${this.#tonic.name} ${this.#variant} minor`;
|
|
761
|
+
}
|
|
705
762
|
return `${this.#tonic.name} ${this.isMinor ? "minor" : "major"}`;
|
|
706
763
|
}
|
|
707
764
|
};
|
|
@@ -717,9 +774,13 @@ var Progression = class _Progression {
|
|
|
717
774
|
* @param key Optional key context for analysis methods.
|
|
718
775
|
*/
|
|
719
776
|
constructor(chords, key) {
|
|
720
|
-
this.#chords = Object.freeze(
|
|
777
|
+
this.#chords = Object.freeze(_Progression.#attachKey(chords, key));
|
|
721
778
|
this.#key = key;
|
|
722
779
|
}
|
|
780
|
+
/** Attach a progression key to chord members that do not already carry one. */
|
|
781
|
+
static #attachKey(chords, key) {
|
|
782
|
+
return key === void 0 ? [...chords] : chords.map((chord) => chord.key === void 0 ? chord.withKey(key) : chord);
|
|
783
|
+
}
|
|
723
784
|
/**
|
|
724
785
|
* Build a progression from the {@link ChordSpan} records the generators
|
|
725
786
|
* return, keeping their order.
|
|
@@ -740,11 +801,10 @@ var Progression = class _Progression {
|
|
|
740
801
|
* ```
|
|
741
802
|
*/
|
|
742
803
|
static fromSpans(spans, key) {
|
|
743
|
-
const chords = spans.map(
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
return key === void 0 ? new _Progression(chords) : new _Progression(chords, key);
|
|
804
|
+
const chords = spans.map(
|
|
805
|
+
(span) => Chord.from(makeChord(span.rootPc, span.quality, span.bassPc))
|
|
806
|
+
);
|
|
807
|
+
return new _Progression(chords, key);
|
|
748
808
|
}
|
|
749
809
|
/**
|
|
750
810
|
* Rebuild a progression from its {@link Progression.toJSON} output.
|
|
@@ -831,21 +891,23 @@ var Progression = class _Progression {
|
|
|
831
891
|
* @throws If any chord admits no voicing within the given ranges.
|
|
832
892
|
*/
|
|
833
893
|
voice(opts) {
|
|
894
|
+
const key = opts?.key ?? this.#key?.scale;
|
|
834
895
|
return voiceProgression(
|
|
835
896
|
this.#chords.map((chord) => chord.data),
|
|
836
|
-
opts
|
|
897
|
+
key === void 0 ? opts : { ...opts, key }
|
|
837
898
|
);
|
|
838
899
|
}
|
|
839
900
|
/**
|
|
840
901
|
* The Roman numeral of each chord in a key.
|
|
841
902
|
*
|
|
842
903
|
* @param key Key to analyze in; falls back to the carried context.
|
|
904
|
+
* @param opts Applied-numeral rendering options.
|
|
843
905
|
* @returns One numeral per chord.
|
|
844
906
|
* @throws If no key is given and none is carried.
|
|
845
907
|
*/
|
|
846
|
-
roman(key) {
|
|
908
|
+
roman(key, opts) {
|
|
847
909
|
const resolved = this.#resolveKey(key);
|
|
848
|
-
return this.#chords.map((chord) => chord.roman(resolved));
|
|
910
|
+
return this.#chords.map((chord) => chord.roman(resolved, opts));
|
|
849
911
|
}
|
|
850
912
|
/**
|
|
851
913
|
* The harmonic function of each chord in a key.
|
|
@@ -865,17 +927,26 @@ var Progression = class _Progression {
|
|
|
865
927
|
* progression has fewer than two chords.
|
|
866
928
|
*
|
|
867
929
|
* @param key Key to analyze in; falls back to the carried context.
|
|
930
|
+
* @param opts Applied-numeral rendering options.
|
|
868
931
|
* @returns Per-chord analyses and the closing cadence.
|
|
869
932
|
* @throws If no key is given and none is carried.
|
|
870
933
|
*/
|
|
871
|
-
analyze(key) {
|
|
934
|
+
analyze(key, opts) {
|
|
872
935
|
const resolved = this.#resolveKey(key);
|
|
873
|
-
const chords = this.#chords.map((chord) => chord.analyze(resolved));
|
|
936
|
+
const chords = this.#chords.map((chord) => chord.analyze(resolved, opts));
|
|
874
937
|
const from = this.#chords[this.#chords.length - 2];
|
|
875
938
|
const to = this.#chords[this.#chords.length - 1];
|
|
876
939
|
const cadence = from !== void 0 && to !== void 0 ? detectCadence(from.data, to.data, resolved.scale) : null;
|
|
877
940
|
return { chords, cadence };
|
|
878
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Choose one compatible scale for every chord, favoring smooth changes.
|
|
944
|
+
*
|
|
945
|
+
* @returns One scale choice per chord in this progression.
|
|
946
|
+
*/
|
|
947
|
+
scales() {
|
|
948
|
+
return scalesForChanges(this.#chords.map((chord) => chord.data));
|
|
949
|
+
}
|
|
879
950
|
/**
|
|
880
951
|
* The plain progression data, for JSON serialization.
|
|
881
952
|
*
|
|
@@ -905,7 +976,7 @@ var Progression = class _Progression {
|
|
|
905
976
|
const moved = chord.transpose(semitones);
|
|
906
977
|
return key !== void 0 && moved.key === void 0 ? moved.withKey(key) : moved;
|
|
907
978
|
});
|
|
908
|
-
return
|
|
979
|
+
return new _Progression(chords, key);
|
|
909
980
|
}
|
|
910
981
|
/**
|
|
911
982
|
* The chord symbols separated by spaces, so a template literal or a log line
|
|
@@ -916,9 +987,17 @@ var Progression = class _Progression {
|
|
|
916
987
|
toString() {
|
|
917
988
|
return this.#chords.map((chord) => chord.symbol()).join(" ");
|
|
918
989
|
}
|
|
990
|
+
/**
|
|
991
|
+
* The plain progression data, for JSON serialization.
|
|
992
|
+
*
|
|
993
|
+
* Private class fields do not serialize, so this preserves both the chord
|
|
994
|
+
* data and any carried key in `JSON.stringify(progression)`.
|
|
995
|
+
*
|
|
996
|
+
* @returns The chord data sequence and the carried key, if any.
|
|
997
|
+
*/
|
|
919
998
|
toJSON() {
|
|
920
999
|
return {
|
|
921
|
-
chords: this.#chords.map((chord) => chord.
|
|
1000
|
+
chords: this.#chords.map((chord) => chord.toJSON()),
|
|
922
1001
|
key: this.#key?.toJSON()
|
|
923
1002
|
};
|
|
924
1003
|
}
|
|
@@ -938,6 +1017,13 @@ var Progression = class _Progression {
|
|
|
938
1017
|
function hintMatches(hint, pc) {
|
|
939
1018
|
return hint !== void 0 && pc !== void 0 && pitchClassOf(noteToPitchClass(hint)) === pitchClassOf(pc);
|
|
940
1019
|
}
|
|
1020
|
+
function transposeHint(hint, semitones) {
|
|
1021
|
+
if (hint === void 0) {
|
|
1022
|
+
return void 0;
|
|
1023
|
+
}
|
|
1024
|
+
const moved = transposeNote(hint, semitones);
|
|
1025
|
+
return { letter: moved.letter, alter: moved.alter };
|
|
1026
|
+
}
|
|
941
1027
|
function copyChord(data) {
|
|
942
1028
|
const copy = {
|
|
943
1029
|
rootPc: data.rootPc,
|
|
@@ -996,8 +1082,11 @@ var Chord = class _Chord {
|
|
|
996
1082
|
* first key's letters forever. A spelling the caller supplied always wins.
|
|
997
1083
|
*/
|
|
998
1084
|
get #data() {
|
|
1085
|
+
return this.#dataWithKey(this.#key);
|
|
1086
|
+
}
|
|
1087
|
+
/** Build a read view whose missing spellings are derived from `key`. */
|
|
1088
|
+
#dataWithKey(key) {
|
|
999
1089
|
const out = copyChord(this.#given);
|
|
1000
|
-
const key = this.#key;
|
|
1001
1090
|
if (out.rootSpelling === void 0 && key !== void 0) {
|
|
1002
1091
|
out.rootSpelling = spellPitchClass(out.rootPc, key.tonic.data, key.scale);
|
|
1003
1092
|
}
|
|
@@ -1177,11 +1266,12 @@ var Chord = class _Chord {
|
|
|
1177
1266
|
* The chord's Roman numeral in a key.
|
|
1178
1267
|
*
|
|
1179
1268
|
* @param key Key to analyze in; falls back to the carried context.
|
|
1269
|
+
* @param opts Applied-numeral rendering options.
|
|
1180
1270
|
* @returns The Roman numeral string.
|
|
1181
1271
|
* @throws If no key is given and none is carried.
|
|
1182
1272
|
*/
|
|
1183
|
-
roman(key) {
|
|
1184
|
-
return chordToRoman(this.#data, this.#resolveKey(key).scale);
|
|
1273
|
+
roman(key, opts) {
|
|
1274
|
+
return chordToRoman(this.#data, this.#resolveKey(key).scale, opts);
|
|
1185
1275
|
}
|
|
1186
1276
|
/**
|
|
1187
1277
|
* The chord's harmonic function (tonic / subdominant / dominant) in a key.
|
|
@@ -1197,11 +1287,12 @@ var Chord = class _Chord {
|
|
|
1197
1287
|
* Full functional analysis: function, borrowing, and Roman numeral.
|
|
1198
1288
|
*
|
|
1199
1289
|
* @param key Key to analyze in; falls back to the carried context.
|
|
1290
|
+
* @param opts Applied-numeral rendering options.
|
|
1200
1291
|
* @returns The chord analysis.
|
|
1201
1292
|
* @throws If no key is given and none is carried.
|
|
1202
1293
|
*/
|
|
1203
|
-
analyze(key) {
|
|
1204
|
-
return analyzeChord(this.#data, this.#resolveKey(key).scale);
|
|
1294
|
+
analyze(key, opts) {
|
|
1295
|
+
return analyzeChord(this.#data, this.#resolveKey(key).scale, opts);
|
|
1205
1296
|
}
|
|
1206
1297
|
/**
|
|
1207
1298
|
* Whether the chord is borrowed from the parallel mode (modal interchange).
|
|
@@ -1315,6 +1406,14 @@ var Chord = class _Chord {
|
|
|
1315
1406
|
*/
|
|
1316
1407
|
transpose(semitones) {
|
|
1317
1408
|
const moved = transposeChord(this.#given, semitones);
|
|
1409
|
+
const rootSpelling = transposeHint(this.#given.rootSpelling, semitones);
|
|
1410
|
+
const bassSpelling = transposeHint(this.#given.bassSpelling, semitones);
|
|
1411
|
+
if (rootSpelling !== void 0) {
|
|
1412
|
+
moved.rootSpelling = rootSpelling;
|
|
1413
|
+
}
|
|
1414
|
+
if (bassSpelling !== void 0) {
|
|
1415
|
+
moved.bassSpelling = bassSpelling;
|
|
1416
|
+
}
|
|
1318
1417
|
return new _Chord(moved, this.#key?.transpose(semitones));
|
|
1319
1418
|
}
|
|
1320
1419
|
/**
|
|
@@ -1368,8 +1467,8 @@ var Chord = class _Chord {
|
|
|
1368
1467
|
* spelling of its own.
|
|
1369
1468
|
*/
|
|
1370
1469
|
spell(key) {
|
|
1371
|
-
const data = this.#data;
|
|
1372
1470
|
const resolved = key ?? this.#key;
|
|
1471
|
+
const data = this.#dataWithKey(resolved);
|
|
1373
1472
|
let tones;
|
|
1374
1473
|
if (resolved === void 0) {
|
|
1375
1474
|
const root = data.rootSpelling;
|
|
@@ -1417,7 +1516,7 @@ var Chord = class _Chord {
|
|
|
1417
1516
|
* @returns A copy of the underlying plain chord object.
|
|
1418
1517
|
*/
|
|
1419
1518
|
toJSON() {
|
|
1420
|
-
return this
|
|
1519
|
+
return copyChord(this.#given);
|
|
1421
1520
|
}
|
|
1422
1521
|
/**
|
|
1423
1522
|
* The chord symbol, so a template literal or a log line reads as the chord.
|
|
@@ -1446,4 +1545,4 @@ export {
|
|
|
1446
1545
|
Progression,
|
|
1447
1546
|
Chord
|
|
1448
1547
|
};
|
|
1449
|
-
//# sourceMappingURL=chunk-
|
|
1548
|
+
//# sourceMappingURL=chunk-3KX76RLG.js.map
|