@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
|
@@ -8,23 +8,202 @@ import {
|
|
|
8
8
|
isScaleTone,
|
|
9
9
|
majorKey,
|
|
10
10
|
makeChord,
|
|
11
|
-
minorKey,
|
|
12
11
|
scaleTonesInDegreeOrder
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-D2FAJBYL.js";
|
|
13
|
+
import {
|
|
14
|
+
InvalidInputError,
|
|
15
|
+
assertFiniteNumber,
|
|
16
|
+
assertGenerationBudget,
|
|
17
|
+
assertInteger,
|
|
18
|
+
pitchClassOf
|
|
19
|
+
} from "./chunk-XEBG3G53.js";
|
|
14
20
|
|
|
15
|
-
// src/analyze/
|
|
16
|
-
function
|
|
17
|
-
return (
|
|
21
|
+
// src/analyze/detect/index.ts
|
|
22
|
+
function uniquePitchClasses(pitches) {
|
|
23
|
+
return [...new Set(pitches.map(pitchClassOf))].sort((a, b) => a - b);
|
|
24
|
+
}
|
|
25
|
+
function assertPitches(pitches) {
|
|
26
|
+
assertGenerationBudget(pitches.length, "detection pitches");
|
|
27
|
+
for (let index = 0; index < pitches.length; index += 1) {
|
|
28
|
+
assertFiniteNumber(pitches[index] ?? Number.NaN, `pitches[${index}]`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function detectChord(pitches, opts = {}) {
|
|
32
|
+
assertPitches(pitches);
|
|
33
|
+
if (opts.bassPc !== void 0) assertFiniteNumber(opts.bassPc, "bassPc");
|
|
34
|
+
const input = uniquePitchClasses(pitches);
|
|
35
|
+
if (input.length === 0) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const inputKind = opts.input === void 0 || opts.input === "auto" ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11) ? "pitchClass" : "midi" : opts.input;
|
|
39
|
+
const bassPc = opts.bassPc !== void 0 ? pitchClassOf(opts.bassPc) : inputKind === "midi" ? pitchClassOf(pitches.reduce((lowest, pitch) => Math.min(lowest, pitch), Infinity)) : void 0;
|
|
40
|
+
const inputSet = new Set(input);
|
|
41
|
+
const matches = [];
|
|
42
|
+
const qualities = chordQualities();
|
|
43
|
+
for (const rootPc of input) {
|
|
44
|
+
for (const quality of qualities) {
|
|
45
|
+
const chord = makeChord(rootPc, quality);
|
|
46
|
+
const tones = chordPitchClasses(chord);
|
|
47
|
+
const toneSet = new Set(tones);
|
|
48
|
+
const missingPcs = tones.filter((pc) => !inputSet.has(pc));
|
|
49
|
+
const fifthPc = pitchClassOf(rootPc + 7);
|
|
50
|
+
if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const extraPcs = input.filter((pc) => !toneSet.has(pc));
|
|
54
|
+
const bassIndex = bassPc === void 0 ? -1 : chord.intervals.findIndex((iv) => pitchClassOf(rootPc + iv) === bassPc);
|
|
55
|
+
const inversion = bassPc === void 0 || bassIndex < 0 ? null : bassIndex;
|
|
56
|
+
const match = {
|
|
57
|
+
rootPc,
|
|
58
|
+
quality,
|
|
59
|
+
missingPcs,
|
|
60
|
+
extraPcs,
|
|
61
|
+
exact: extraPcs.length === 0 && missingPcs.length === 0,
|
|
62
|
+
inversion
|
|
63
|
+
};
|
|
64
|
+
if (bassPc !== void 0 && bassPc !== rootPc) {
|
|
65
|
+
match.bassPc = bassPc;
|
|
66
|
+
}
|
|
67
|
+
matches.push(match);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
matches.sort((a, b) => {
|
|
71
|
+
if (a.extraPcs.length !== b.extraPcs.length) {
|
|
72
|
+
return a.extraPcs.length - b.extraPcs.length;
|
|
73
|
+
}
|
|
74
|
+
if (a.missingPcs.length !== b.missingPcs.length) {
|
|
75
|
+
return a.missingPcs.length - b.missingPcs.length;
|
|
76
|
+
}
|
|
77
|
+
if (a.inversion === 0 !== (b.inversion === 0)) {
|
|
78
|
+
return a.inversion === 0 ? -1 : 1;
|
|
79
|
+
}
|
|
80
|
+
const aSize = chordPitchClasses(makeChord(a.rootPc, a.quality)).length;
|
|
81
|
+
const bSize = chordPitchClasses(makeChord(b.rootPc, b.quality)).length;
|
|
82
|
+
return bSize - aSize;
|
|
83
|
+
});
|
|
84
|
+
return matches;
|
|
85
|
+
}
|
|
86
|
+
function detectChordBest(pitches, opts = {}) {
|
|
87
|
+
const best = detectChord(pitches, opts)[0];
|
|
88
|
+
if (!best) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return makeChord(best.rootPc, best.quality, best.bassPc);
|
|
92
|
+
}
|
|
93
|
+
var MINOR_VARIANTS = [
|
|
94
|
+
{ variant: "natural", mask: NATURAL_MINOR_MASK },
|
|
95
|
+
{ variant: "harmonic", mask: HARMONIC_MINOR_MASK },
|
|
96
|
+
{ variant: "melodic", mask: MELODIC_MINOR_MASK }
|
|
97
|
+
];
|
|
98
|
+
var MAJOR_VARIANTS = [{ variant: "major", mask: MAJOR_MASK }];
|
|
99
|
+
function detectKey(pitches, opts = {}) {
|
|
100
|
+
assertPitches(pitches);
|
|
101
|
+
const input = uniquePitchClasses(pitches);
|
|
102
|
+
if (input.length === 0) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const weights = opts.weights;
|
|
106
|
+
if (weights !== void 0 && weights.length !== pitches.length) {
|
|
107
|
+
throw new InvalidInputError("weights must have one entry per pitch");
|
|
108
|
+
}
|
|
109
|
+
const counts = /* @__PURE__ */ new Map();
|
|
110
|
+
let total = 0;
|
|
111
|
+
for (let index = 0; index < pitches.length; index += 1) {
|
|
112
|
+
const weight = weights === void 0 ? 1 : weights[index] ?? 0;
|
|
113
|
+
assertFiniteNumber(weight, `weights[${index}]`);
|
|
114
|
+
if (weight <= 0) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const pc = pitchClassOf(pitches[index] ?? 0);
|
|
118
|
+
counts.set(pc, (counts.get(pc) ?? 0) + weight);
|
|
119
|
+
total += weight;
|
|
120
|
+
}
|
|
121
|
+
if (total === 0) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const results = [];
|
|
125
|
+
for (let tonic = 0; tonic < 12; tonic += 1) {
|
|
126
|
+
for (const mode of ["major", "minor"]) {
|
|
127
|
+
const variants = mode === "major" ? MAJOR_VARIANTS : MINOR_VARIANTS;
|
|
128
|
+
let inScale = 0;
|
|
129
|
+
let weighted = Number.NEGATIVE_INFINITY;
|
|
130
|
+
let best = variants[0];
|
|
131
|
+
for (const candidate of variants) {
|
|
132
|
+
let variantInScale = 0;
|
|
133
|
+
let variantWeighted = 0;
|
|
134
|
+
for (const pc of input) {
|
|
135
|
+
const offset = (pc - tonic + 12) % 12;
|
|
136
|
+
if (candidate.mask >> offset & 1) {
|
|
137
|
+
variantInScale += 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const [pc, count] of counts) {
|
|
141
|
+
const offset = (pc - tonic + 12) % 12;
|
|
142
|
+
if (candidate.mask >> offset & 1) {
|
|
143
|
+
variantWeighted += count;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (variantWeighted > weighted) {
|
|
147
|
+
weighted = variantWeighted;
|
|
148
|
+
inScale = variantInScale;
|
|
149
|
+
best = candidate;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
weighted += (counts.get(tonic) ?? 0) * 0.5;
|
|
153
|
+
results.push({
|
|
154
|
+
key: { rootPc: tonic, modeMask12: best.mask },
|
|
155
|
+
mode,
|
|
156
|
+
variant: best.variant,
|
|
157
|
+
fit: inScale / input.length,
|
|
158
|
+
score: weighted / total
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
results.sort((a, b) => b.score - a.score);
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
var DEFAULT_VELOCITY = 100;
|
|
166
|
+
function detectKeyFromNotes(notes) {
|
|
167
|
+
const sounding = notes.filter((note) => note.durationBeat > 0);
|
|
168
|
+
return detectKey(
|
|
169
|
+
sounding.map((note) => note.pitch),
|
|
170
|
+
{ weights: sounding.map((note) => note.durationBeat * (note.velocity ?? DEFAULT_VELOCITY)) }
|
|
171
|
+
);
|
|
18
172
|
}
|
|
173
|
+
function detectKeyBest(pitches, opts = {}) {
|
|
174
|
+
return detectKey(pitches, opts)[0] ?? null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/analyze/functional/internal.ts
|
|
19
178
|
function degreeRootPc(degreeNumber, key) {
|
|
20
179
|
const tones = scaleTonesInDegreeOrder(key);
|
|
21
180
|
if (tones.length === 0) {
|
|
22
|
-
return
|
|
181
|
+
return pitchClassOf(key.rootPc);
|
|
182
|
+
}
|
|
183
|
+
const index = ((degreeNumber - 1) % tones.length + tones.length) % tones.length;
|
|
184
|
+
return tones[index] ?? pitchClassOf(key.rootPc);
|
|
185
|
+
}
|
|
186
|
+
function loweredDegrees(key) {
|
|
187
|
+
const tones = scaleTonesInDegreeOrder(key);
|
|
188
|
+
const out = /* @__PURE__ */ new Set();
|
|
189
|
+
if (tones.length !== 7) {
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
const major = scaleTonesInDegreeOrder({ rootPc: pitchClassOf(key.rootPc), modeMask12: MAJOR_MASK });
|
|
193
|
+
for (let index = 0; index < 7; index += 1) {
|
|
194
|
+
const tone = tones[index];
|
|
195
|
+
const majorTone = major[index];
|
|
196
|
+
if (tone !== void 0 && majorTone !== void 0 && pitchClassOf(majorTone - tone) === 1) {
|
|
197
|
+
out.add(index + 1);
|
|
198
|
+
}
|
|
23
199
|
}
|
|
24
|
-
return
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
function romanReference(key) {
|
|
203
|
+
return scaleTonesInDegreeOrder(key).length === 7 ? key : majorKey(pitchClassOf(key.rootPc));
|
|
25
204
|
}
|
|
26
205
|
function isNeapolitan(chord, key) {
|
|
27
|
-
return
|
|
206
|
+
return pitchClassOf(chord.rootPc - key.rootPc) === 1 && chord.quality === "maj";
|
|
28
207
|
}
|
|
29
208
|
|
|
30
209
|
// src/analyze/functional/roman.ts
|
|
@@ -95,7 +274,17 @@ var ROMAN_STYLE = {
|
|
|
95
274
|
"7b13": { lower: false, suffix: "7b13" },
|
|
96
275
|
"11": { lower: false, suffix: "11" },
|
|
97
276
|
"13": { lower: false, suffix: "13" },
|
|
98
|
-
"5": { lower: false, suffix: "5" }
|
|
277
|
+
"5": { lower: false, suffix: "5" },
|
|
278
|
+
"7sus4": { lower: false, suffix: "7sus4" },
|
|
279
|
+
"7b5": { lower: false, suffix: "7b5" },
|
|
280
|
+
"7alt": { lower: false, suffix: "7alt" },
|
|
281
|
+
"13b9": { lower: false, suffix: "13b9" },
|
|
282
|
+
maj13: { lower: false, suffix: "maj13" },
|
|
283
|
+
"maj7#11": { lower: false, suffix: "maj7#11" },
|
|
284
|
+
min11: { lower: true, suffix: "11" },
|
|
285
|
+
min13: { lower: true, suffix: "13" },
|
|
286
|
+
minAdd9: { lower: true, suffix: "add9" },
|
|
287
|
+
"min6/9": { lower: true, suffix: "69" }
|
|
99
288
|
};
|
|
100
289
|
var SUFFIX_QUALITY = new Map(
|
|
101
290
|
Object.entries(ROMAN_STYLE).map(
|
|
@@ -126,28 +315,37 @@ function parseInversion(figures) {
|
|
|
126
315
|
function parseSimpleRoman(text, key) {
|
|
127
316
|
const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());
|
|
128
317
|
if (!match) {
|
|
129
|
-
throw new
|
|
318
|
+
throw new InvalidInputError(`Invalid Roman numeral: ${text}`);
|
|
130
319
|
}
|
|
131
320
|
const accidental = match[1] === "b" ? -1 : match[1] === "#" ? 1 : 0;
|
|
132
321
|
const glyph = (match[2] ?? "").toUpperCase();
|
|
133
322
|
const degreeNumber = ROMAN_TO_DEGREE[glyph];
|
|
134
323
|
if (degreeNumber === void 0) {
|
|
135
|
-
throw new
|
|
324
|
+
throw new InvalidInputError(`Invalid Roman numeral: ${text}`);
|
|
136
325
|
}
|
|
137
326
|
const isUpper = (match[2] ?? "")[0] === (match[2] ?? "")[0]?.toUpperCase();
|
|
138
327
|
const suffix = match[3] ?? "";
|
|
139
|
-
const
|
|
328
|
+
const frame = romanReference(key);
|
|
329
|
+
const redundantFlat = accidental === -1 && loweredDegrees(frame).has(degreeNumber);
|
|
330
|
+
const rootPc = pitchClassOf(degreeRootPc(degreeNumber, frame) + (redundantFlat ? 0 : accidental));
|
|
140
331
|
const canonical = SUFFIX_QUALITY.get(`${isUpper ? "u" : "l"}:${suffix}`);
|
|
141
332
|
if (canonical !== void 0) {
|
|
142
333
|
return { rootPc, quality: canonical, inversion: 0 };
|
|
143
334
|
}
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
335
|
+
const parsedSuffix = /^(o|°|dim|ø|\+|aug)?(maj7|M7)?(65|64|43|42|7|6|2)?$/.exec(suffix);
|
|
336
|
+
if (!parsedSuffix) {
|
|
337
|
+
throw new InvalidInputError(`Unsupported suffix "${suffix}" in Roman numeral: ${text}`);
|
|
338
|
+
}
|
|
339
|
+
const qualityMarker = parsedSuffix[1] ?? "";
|
|
340
|
+
const isDim = qualityMarker === "o" || qualityMarker === "\xB0" || qualityMarker === "dim";
|
|
341
|
+
const isAug = qualityMarker === "+" || qualityMarker === "aug";
|
|
342
|
+
const isHalfDim = qualityMarker === "\xF8";
|
|
343
|
+
const explicitMaj7 = parsedSuffix[2] !== void 0;
|
|
344
|
+
const figures = parsedSuffix[3] ?? "";
|
|
149
345
|
if (figures !== "" && !INVERSION_FIGURES.has(figures)) {
|
|
150
|
-
throw new
|
|
346
|
+
throw new InvalidInputError(
|
|
347
|
+
`Unsupported figured-bass or extension "${figures}" in Roman numeral: ${text}`
|
|
348
|
+
);
|
|
151
349
|
}
|
|
152
350
|
const { inversion, seventh } = parseInversion(figures);
|
|
153
351
|
const hasSeventh = explicitMaj7 || seventh || isHalfDim;
|
|
@@ -176,7 +374,7 @@ function parseSimpleRoman(text, key) {
|
|
|
176
374
|
function chordFromParsed(parsed) {
|
|
177
375
|
const chord = makeChord(parsed.rootPc, parsed.quality);
|
|
178
376
|
if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {
|
|
179
|
-
chord.bassPc =
|
|
377
|
+
chord.bassPc = pitchClassOf(parsed.rootPc + (chord.intervals[parsed.inversion] ?? 0));
|
|
180
378
|
}
|
|
181
379
|
return chord;
|
|
182
380
|
}
|
|
@@ -196,26 +394,32 @@ function romanStyle(quality) {
|
|
|
196
394
|
return ROMAN_STYLE[quality];
|
|
197
395
|
}
|
|
198
396
|
function romanSpelling(rootPc, key) {
|
|
199
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
200
|
-
const diatonic = tones.indexOf(
|
|
397
|
+
const tones = scaleTonesInDegreeOrder(romanReference(key));
|
|
398
|
+
const diatonic = tones.indexOf(pitchClassOf(rootPc));
|
|
201
399
|
if (diatonic >= 0) {
|
|
202
400
|
return { degreeNumber: diatonic + 1, accidental: "" };
|
|
203
401
|
}
|
|
402
|
+
const lowered = loweredDegrees(romanReference(key));
|
|
403
|
+
for (let i = 0; i < tones.length; i += 1) {
|
|
404
|
+
if (lowered.has(i + 1) && pitchClassOf((tones[i] ?? 0) + 1) === pitchClassOf(rootPc)) {
|
|
405
|
+
return { degreeNumber: i + 1, accidental: "#" };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
204
408
|
for (let i = 0; i < tones.length; i += 1) {
|
|
205
409
|
const degreeNumber2 = i + 1;
|
|
206
410
|
if (degreeNumber2 === 1 || degreeNumber2 === 5) {
|
|
207
411
|
continue;
|
|
208
412
|
}
|
|
209
|
-
if (
|
|
413
|
+
if (pitchClassOf((tones[i] ?? 0) - 1) === pitchClassOf(rootPc)) {
|
|
210
414
|
return { degreeNumber: degreeNumber2, accidental: "b" };
|
|
211
415
|
}
|
|
212
416
|
}
|
|
213
417
|
for (let i = 0; i < tones.length; i += 1) {
|
|
214
|
-
if (
|
|
418
|
+
if (pitchClassOf((tones[i] ?? 0) + 1) === pitchClassOf(rootPc)) {
|
|
215
419
|
return { degreeNumber: i + 1, accidental: "#" };
|
|
216
420
|
}
|
|
217
421
|
}
|
|
218
|
-
const [degreeNumber, accidental] = OFFSET_SPELLING[
|
|
422
|
+
const [degreeNumber, accidental] = OFFSET_SPELLING[pitchClassOf(rootPc - key.rootPc)] ?? [1, ""];
|
|
219
423
|
return { degreeNumber, accidental };
|
|
220
424
|
}
|
|
221
425
|
function baseMarker(quality) {
|
|
@@ -249,14 +453,65 @@ var SEVENTH_FIGURE_QUALITIES = /* @__PURE__ */ new Set([
|
|
|
249
453
|
"aug7",
|
|
250
454
|
"augMaj7"
|
|
251
455
|
]);
|
|
252
|
-
function
|
|
456
|
+
function numeralFor(degreeNumber, lower) {
|
|
457
|
+
const glyph = ROMAN[degreeNumber - 1];
|
|
458
|
+
if (glyph === void 0) {
|
|
459
|
+
throw new InvalidInputError(`no Roman numeral for scale degree ${degreeNumber}`);
|
|
460
|
+
}
|
|
461
|
+
return lower ? glyph.toLowerCase() : glyph;
|
|
462
|
+
}
|
|
463
|
+
function isDiatonicChord(chord, key) {
|
|
464
|
+
return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
|
|
465
|
+
}
|
|
466
|
+
function isAppliedDominantSonority(chord) {
|
|
467
|
+
const has = (semitones) => chord.intervals.some((i) => pitchClassOf(i) === semitones);
|
|
468
|
+
return chord.quality === "maj" || has(4) && has(10);
|
|
469
|
+
}
|
|
470
|
+
var LEADING_TONE_QUALITIES = /* @__PURE__ */ new Set(["dim", "dim7", "m7b5"]);
|
|
471
|
+
function degreeTriad(tones, index) {
|
|
472
|
+
const root = tones[index] ?? 0;
|
|
473
|
+
return {
|
|
474
|
+
third: pitchClassOf((tones[(index + 2) % tones.length] ?? 0) - root),
|
|
475
|
+
fifth: pitchClassOf((tones[(index + 4) % tones.length] ?? 0) - root)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function appliedTarget(chord, key) {
|
|
479
|
+
const dominant = isAppliedDominantSonority(chord);
|
|
480
|
+
const leadingTone = LEADING_TONE_QUALITIES.has(chord.quality);
|
|
481
|
+
if (!dominant && !leadingTone) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
const tones = scaleTonesInDegreeOrder(romanReference(key));
|
|
485
|
+
for (let index = 1; index < tones.length; index += 1) {
|
|
486
|
+
const targetRoot = tones[index];
|
|
487
|
+
if (targetRoot === void 0) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const triad = degreeTriad(tones, index);
|
|
491
|
+
if (triad.fifth === 6) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const expectedRoot = pitchClassOf(targetRoot + (dominant ? 7 : -1));
|
|
495
|
+
if (expectedRoot === pitchClassOf(chord.rootPc)) {
|
|
496
|
+
return { degreeNumber: index + 1, rootPc: targetRoot, lower: triad.third === 3 };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
function chordToRoman(chord, key, opts = {}) {
|
|
502
|
+
if (opts.applied === true && !isDiatonicChord(chord, key)) {
|
|
503
|
+
const target = appliedTarget(chord, key);
|
|
504
|
+
if (target !== null) {
|
|
505
|
+
const local = chordToRoman(chord, majorKey(target.rootPc));
|
|
506
|
+
return `${local}/${numeralFor(target.degreeNumber, target.lower)}`;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
253
509
|
const { degreeNumber, accidental } = romanSpelling(chord.rootPc, key);
|
|
254
510
|
const { lower, suffix } = romanStyle(chord.quality);
|
|
255
|
-
const
|
|
256
|
-
const cased = lower ? numeral.toLowerCase() : numeral;
|
|
511
|
+
const cased = numeralFor(degreeNumber, lower);
|
|
257
512
|
let inversion = 0;
|
|
258
513
|
if (chord.bassPc !== void 0) {
|
|
259
|
-
const idx = chord.intervals.findIndex((iv) =>
|
|
514
|
+
const idx = chord.intervals.findIndex((iv) => pitchClassOf(chord.rootPc + iv) === chord.bassPc);
|
|
260
515
|
if (idx > 0) {
|
|
261
516
|
inversion = idx;
|
|
262
517
|
}
|
|
@@ -272,7 +527,7 @@ function chordToRoman(chord, key) {
|
|
|
272
527
|
|
|
273
528
|
// src/analyze/functional/borrowed.ts
|
|
274
529
|
function isHarmonicMinorAlteration(chord, key) {
|
|
275
|
-
return isMinorKey(key) && isDiatonic(chord, { rootPc:
|
|
530
|
+
return isMinorKey(key) && isDiatonic(chord, { rootPc: pitchClassOf(key.rootPc), modeMask12: HARMONIC_MINOR_MASK });
|
|
276
531
|
}
|
|
277
532
|
function isBorrowedChord(chord, key) {
|
|
278
533
|
if (isDiatonic(chord, key)) {
|
|
@@ -291,7 +546,7 @@ function borrowedSource(chord, key) {
|
|
|
291
546
|
return "neapolitan";
|
|
292
547
|
}
|
|
293
548
|
if (isBorrowedChord(chord, key)) {
|
|
294
|
-
return isMinorKey(key) ? "
|
|
549
|
+
return isMinorKey(key) ? "parallelMajor" : "parallelMinor";
|
|
295
550
|
}
|
|
296
551
|
return null;
|
|
297
552
|
}
|
|
@@ -329,14 +584,35 @@ function isMinorKey(key) {
|
|
|
329
584
|
return Boolean(hasMinorThird) && !hasMajorThird2;
|
|
330
585
|
}
|
|
331
586
|
function functionOf(chord, key) {
|
|
332
|
-
|
|
587
|
+
const offset = pitchClassOf(chord.rootPc - key.rootPc);
|
|
588
|
+
if (isNeapolitan(chord, key)) {
|
|
589
|
+
return "subdominant";
|
|
590
|
+
}
|
|
591
|
+
if (isAppliedDominant(chord, key)) {
|
|
592
|
+
return "dominant";
|
|
593
|
+
}
|
|
594
|
+
if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {
|
|
595
|
+
return "subdominant";
|
|
596
|
+
}
|
|
597
|
+
return FUNCTION_BY_OFFSET[offset] ?? "tonic";
|
|
598
|
+
}
|
|
599
|
+
function isAppliedDominant(chord, key) {
|
|
600
|
+
if (isDiatonic(chord, key)) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
const root = pitchClassOf(chord.rootPc);
|
|
604
|
+
const resolvesTo = (step) => isScaleTone(pitchClassOf(root + step), key);
|
|
605
|
+
if (hasMajorThird(chord) && chord.intervals.some((i) => pitchClassOf(i) === 10)) {
|
|
606
|
+
return resolvesTo(5) || resolvesTo(11);
|
|
607
|
+
}
|
|
608
|
+
return isDiminishedQuality(chord.quality) && resolvesTo(1);
|
|
333
609
|
}
|
|
334
610
|
function isDiatonic(chord, key) {
|
|
335
611
|
return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
|
|
336
612
|
}
|
|
337
613
|
function parallelKey(key) {
|
|
338
614
|
return {
|
|
339
|
-
rootPc:
|
|
615
|
+
rootPc: pitchClassOf(key.rootPc),
|
|
340
616
|
modeMask12: isMinorKey(key) ? MAJOR_MASK : NATURAL_MINOR_MASK
|
|
341
617
|
};
|
|
342
618
|
}
|
|
@@ -344,40 +620,28 @@ function isDiminishedQuality(quality) {
|
|
|
344
620
|
return quality === "dim" || quality === "dim7" || quality === "m7b5";
|
|
345
621
|
}
|
|
346
622
|
function hasMajorThird(chord) {
|
|
347
|
-
return chord.intervals.some((interval) =>
|
|
348
|
-
}
|
|
349
|
-
function qualityAwareFunction(chord, key) {
|
|
350
|
-
const offset = mod12(chord.rootPc - key.rootPc);
|
|
351
|
-
if (isDiminishedQuality(chord.quality) && (offset === 11 || offset === 6)) {
|
|
352
|
-
return "dominant";
|
|
353
|
-
}
|
|
354
|
-
if (isNeapolitan(chord, key)) {
|
|
355
|
-
return "subdominant";
|
|
356
|
-
}
|
|
357
|
-
if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {
|
|
358
|
-
return "subdominant";
|
|
359
|
-
}
|
|
360
|
-
return functionOf(chord, key);
|
|
623
|
+
return chord.intervals.some((interval) => pitchClassOf(interval) === 4);
|
|
361
624
|
}
|
|
362
625
|
function analyzeChord(chord, key) {
|
|
363
626
|
const source = borrowedSource(chord, key);
|
|
364
627
|
return {
|
|
365
|
-
function:
|
|
628
|
+
function: functionOf(chord, key),
|
|
366
629
|
borrowed: source !== null,
|
|
367
630
|
source,
|
|
368
631
|
roman: chordToRoman(chord, key)
|
|
369
632
|
};
|
|
370
633
|
}
|
|
371
634
|
function secondaryDominant(targetDegree, key) {
|
|
635
|
+
assertInteger(targetDegree, "targetDegree", 0, 6);
|
|
372
636
|
const targetRoot = degreeRootPc(targetDegree + 1, key);
|
|
373
|
-
return makeChord(
|
|
637
|
+
return makeChord(pitchClassOf(targetRoot + 7), "dom7");
|
|
374
638
|
}
|
|
375
639
|
|
|
376
640
|
// src/analyze/functional/cadence.ts
|
|
377
641
|
function detectCadence(from, to, key) {
|
|
378
|
-
const tonic =
|
|
379
|
-
const fromOffset =
|
|
380
|
-
const toOffset =
|
|
642
|
+
const tonic = pitchClassOf(key.rootPc);
|
|
643
|
+
const fromOffset = pitchClassOf(from.rootPc - tonic);
|
|
644
|
+
const toOffset = pitchClassOf(to.rootPc - tonic);
|
|
381
645
|
const deceptiveTargets = isMinorKey(key) ? [8] : [8, 9];
|
|
382
646
|
if (fromOffset === 7 && toOffset === 0) {
|
|
383
647
|
return "authentic";
|
|
@@ -394,124 +658,12 @@ function detectCadence(from, to, key) {
|
|
|
394
658
|
return null;
|
|
395
659
|
}
|
|
396
660
|
|
|
397
|
-
// src/analyze/detect/index.ts
|
|
398
|
-
function pitchClass(value) {
|
|
399
|
-
return (Math.trunc(value) % 12 + 12) % 12;
|
|
400
|
-
}
|
|
401
|
-
function uniquePitchClasses(pitches) {
|
|
402
|
-
return [...new Set(pitches.map(pitchClass))].sort((a, b) => a - b);
|
|
403
|
-
}
|
|
404
|
-
function detectChord(pitches) {
|
|
405
|
-
const input = uniquePitchClasses(pitches);
|
|
406
|
-
if (input.length === 0) {
|
|
407
|
-
return [];
|
|
408
|
-
}
|
|
409
|
-
const bassPc = pitchClass(Math.min(...pitches));
|
|
410
|
-
const inputSet = new Set(input);
|
|
411
|
-
const matches = [];
|
|
412
|
-
const qualities = chordQualities();
|
|
413
|
-
for (const rootPc of input) {
|
|
414
|
-
for (const quality of qualities) {
|
|
415
|
-
const chord = makeChord(rootPc, quality);
|
|
416
|
-
const tones = chordPitchClasses(chord);
|
|
417
|
-
const toneSet = new Set(tones);
|
|
418
|
-
const missingPcs = tones.filter((pc) => !inputSet.has(pc));
|
|
419
|
-
const fifthPc = pitchClass(rootPc + 7);
|
|
420
|
-
if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
const extraPcs = input.filter((pc) => !toneSet.has(pc));
|
|
424
|
-
const bassIndex = chord.intervals.findIndex((iv) => pitchClass(rootPc + iv) === bassPc);
|
|
425
|
-
const inversion = bassIndex > 0 ? bassIndex : 0;
|
|
426
|
-
const match = {
|
|
427
|
-
rootPc,
|
|
428
|
-
quality,
|
|
429
|
-
missingPcs,
|
|
430
|
-
extraPcs,
|
|
431
|
-
exact: extraPcs.length === 0 && missingPcs.length === 0,
|
|
432
|
-
inversion
|
|
433
|
-
};
|
|
434
|
-
if (inversion > 0) {
|
|
435
|
-
match.bassPc = bassPc;
|
|
436
|
-
}
|
|
437
|
-
matches.push(match);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
matches.sort((a, b) => {
|
|
441
|
-
if (a.extraPcs.length !== b.extraPcs.length) {
|
|
442
|
-
return a.extraPcs.length - b.extraPcs.length;
|
|
443
|
-
}
|
|
444
|
-
if (a.missingPcs.length !== b.missingPcs.length) {
|
|
445
|
-
return a.missingPcs.length - b.missingPcs.length;
|
|
446
|
-
}
|
|
447
|
-
if (a.inversion === 0 !== (b.inversion === 0)) {
|
|
448
|
-
return a.inversion === 0 ? -1 : 1;
|
|
449
|
-
}
|
|
450
|
-
const aSize = chordPitchClasses(makeChord(a.rootPc, a.quality)).length;
|
|
451
|
-
const bSize = chordPitchClasses(makeChord(b.rootPc, b.quality)).length;
|
|
452
|
-
return bSize - aSize;
|
|
453
|
-
});
|
|
454
|
-
return matches;
|
|
455
|
-
}
|
|
456
|
-
function detectChordBest(pitches) {
|
|
457
|
-
const best = detectChord(pitches)[0];
|
|
458
|
-
if (!best) {
|
|
459
|
-
return null;
|
|
460
|
-
}
|
|
461
|
-
return makeChord(best.rootPc, best.quality, best.bassPc);
|
|
462
|
-
}
|
|
463
|
-
var MINOR_MASK_VARIANTS = [NATURAL_MINOR_MASK, HARMONIC_MINOR_MASK, MELODIC_MINOR_MASK];
|
|
464
|
-
function detectKey(pitches) {
|
|
465
|
-
const input = uniquePitchClasses(pitches);
|
|
466
|
-
if (input.length === 0) {
|
|
467
|
-
return [];
|
|
468
|
-
}
|
|
469
|
-
const counts = /* @__PURE__ */ new Map();
|
|
470
|
-
for (const pc of pitches.map(pitchClass)) {
|
|
471
|
-
counts.set(pc, (counts.get(pc) ?? 0) + 1);
|
|
472
|
-
}
|
|
473
|
-
const total = pitches.length;
|
|
474
|
-
const results = [];
|
|
475
|
-
for (let tonic = 0; tonic < 12; tonic += 1) {
|
|
476
|
-
for (const mode of ["major", "minor"]) {
|
|
477
|
-
const key = mode === "major" ? majorKey(tonic) : minorKey(tonic);
|
|
478
|
-
const masks = mode === "major" ? [MAJOR_MASK] : MINOR_MASK_VARIANTS;
|
|
479
|
-
let inScale = 0;
|
|
480
|
-
let weighted = Number.NEGATIVE_INFINITY;
|
|
481
|
-
for (const mask of masks) {
|
|
482
|
-
let variantInScale = 0;
|
|
483
|
-
let variantWeighted = 0;
|
|
484
|
-
for (const pc of input) {
|
|
485
|
-
const offset = (pc - tonic + 12) % 12;
|
|
486
|
-
if (mask >> offset & 1) {
|
|
487
|
-
variantInScale += 1;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
for (const [pc, count] of counts) {
|
|
491
|
-
const offset = (pc - tonic + 12) % 12;
|
|
492
|
-
if (mask >> offset & 1) {
|
|
493
|
-
variantWeighted += count;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (variantWeighted > weighted) {
|
|
497
|
-
weighted = variantWeighted;
|
|
498
|
-
inScale = variantInScale;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
weighted += (counts.get(tonic) ?? 0) * 0.5;
|
|
502
|
-
results.push({
|
|
503
|
-
key,
|
|
504
|
-
mode,
|
|
505
|
-
fit: inScale / input.length,
|
|
506
|
-
score: weighted / total
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
results.sort((a, b) => b.score - a.score);
|
|
511
|
-
return results.map(({ key, mode, fit }) => ({ key, mode, fit }));
|
|
512
|
-
}
|
|
513
|
-
|
|
514
661
|
export {
|
|
662
|
+
detectChord,
|
|
663
|
+
detectChordBest,
|
|
664
|
+
detectKey,
|
|
665
|
+
detectKeyFromNotes,
|
|
666
|
+
detectKeyBest,
|
|
515
667
|
romanToChord,
|
|
516
668
|
chordToRoman,
|
|
517
669
|
isMinorKey,
|
|
@@ -522,9 +674,6 @@ export {
|
|
|
522
674
|
secondaryDominant,
|
|
523
675
|
isBorrowedChord,
|
|
524
676
|
borrowedSource,
|
|
525
|
-
detectCadence
|
|
526
|
-
detectChord,
|
|
527
|
-
detectChordBest,
|
|
528
|
-
detectKey
|
|
677
|
+
detectCadence
|
|
529
678
|
};
|
|
530
|
-
//# sourceMappingURL=chunk-
|
|
679
|
+
//# sourceMappingURL=chunk-KMHOICIY.js.map
|