@libraz/libcantus 0.9.3 → 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 +2 -2
- package/dist/analyze/index.cjs +39 -1947
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +197 -37
- package/dist/analyze/index.d.ts +197 -37
- 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-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
- 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-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
- package/dist/chunk-D2FAJBYL.js.map +1 -0
- package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
- 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-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
- package/dist/chunk-GQSGS2N4.js.map +1 -0
- package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
- package/dist/chunk-HKGYCQ34.js.map +1 -0
- package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
- package/dist/chunk-I24MV65U.js.map +1 -0
- package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
- 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-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
- 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-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
- package/dist/chunk-NYYWMV2C.js.map +1 -0
- package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
- 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-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
- 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-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
- 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 -647
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +301 -39
- package/dist/core/index.d.ts +301 -39
- package/dist/core/index.js +54 -16
- package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
- package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
- package/dist/generate/index.cjs +28 -4085
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +370 -62
- package/dist/generate/index.d.ts +370 -62
- package/dist/generate/index.js +23 -9
- package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
- package/dist/index-70p168VM.d.ts +192 -0
- package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
- package/dist/index-BtXT6UQ0.d.cts +192 -0
- package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
- package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
- package/dist/{index-Bg5dYMmZ.d.ts → 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-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
- package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
- package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
- package/dist/index.cjs +419 -7383
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +111 -27
- package/dist/model/index.cjs +8 -2562
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +433 -75
- package/dist/model/index.d.ts +433 -75
- 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 -1822
- 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 +30 -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 +24 -6
- package/dist/chunk-2ZWMSJH6.js.map +0 -1
- package/dist/chunk-4MHA432G.js.map +0 -1
- package/dist/chunk-62QZKCJQ.js.map +0 -1
- package/dist/chunk-AKLYPWIN.js.map +0 -1
- package/dist/chunk-C5XYKUVG.js.map +0 -1
- package/dist/chunk-CHMXELDS.js.map +0 -1
- package/dist/chunk-DYWFNC4A.js +0 -152
- package/dist/chunk-DYWFNC4A.js.map +0 -1
- package/dist/chunk-EH6EM37U.js.map +0 -1
- package/dist/chunk-FBQK3ZQN.js +0 -131
- package/dist/chunk-FBQK3ZQN.js.map +0 -1
- package/dist/chunk-GOAW4VYL.js.map +0 -1
- package/dist/chunk-I5R7ZBJS.js.map +0 -1
- package/dist/chunk-IT2O4XFE.js.map +0 -1
- package/dist/chunk-MIWO425M.js.map +0 -1
- package/dist/chunk-SGU7SPLK.js.map +0 -1
- package/dist/index-CGMZNCx6.d.cts +0 -140
- package/dist/index-CGMZNCx6.d.ts +0 -140
- package/dist/index-CnZCo29u.d.cts +0 -108
- package/dist/index-JnF4v9bx.d.ts +0 -108
- package/dist/styled-DZ7GjShb.d.ts +0 -282
- package/dist/styled-ZTbdaVFM.d.cts +0 -282
- package/src/analyze/arrange/index.ts +0 -21
- package/src/analyze/arrange/internal.ts +0 -127
- package/src/analyze/arrange/tension.ts +0 -199
- package/src/analyze/arrange/tracks.ts +0 -312
- package/src/analyze/detect/index.ts +0 -272
- 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 -402
- package/src/analyze/index.ts +0 -49
- package/src/analyze/timeline/index.ts +0 -451
- package/src/analyze/voice/index.ts +0 -339
- package/src/core/event-index/index.ts +0 -104
- package/src/core/index.ts +0 -61
- package/src/core/interval/index.ts +0 -81
- package/src/core/meter/index.ts +0 -302
- package/src/core/pitch/index.ts +0 -311
- package/src/core/random/index.ts +0 -76
- package/src/core/tuning/index.ts +0 -186
- package/src/core/types.ts +0 -29
- package/src/core/validation/index.ts +0 -119
- package/src/generate/bass/index.ts +0 -373
- package/src/generate/countermelody/index.ts +0 -470
- package/src/generate/drums/beat.ts +0 -266
- package/src/generate/drums/euclid.ts +0 -65
- 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 -361
- 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 -351
- package/src/generate/harmonize/index.ts +0 -451
- package/src/generate/index.ts +0 -58
- package/src/generate/motif/index.ts +0 -422
- package/src/generate/progression/index.ts +0 -362
- package/src/generate/reharmony/index.ts +0 -323
- package/src/generate/rhythm/index.ts +0 -175
- package/src/index.ts +0 -13
- package/src/model/chord.ts +0 -442
- 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 -375
- package/src/theory/chordscale/index.ts +0 -374
- package/src/theory/counterpoint/index.ts +0 -230
- package/src/theory/harmony/index.ts +0 -77
- package/src/theory/index.ts +0 -112
- package/src/theory/safety/index.ts +0 -488
- 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 -249
- 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 -216
- package/src/theory/voicing/styled.ts +0 -199
- package/tsconfig.json +0 -23
|
@@ -8,27 +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";
|
|
14
13
|
import {
|
|
14
|
+
InvalidInputError,
|
|
15
15
|
assertFiniteNumber,
|
|
16
|
-
assertGenerationBudget
|
|
17
|
-
|
|
16
|
+
assertGenerationBudget,
|
|
17
|
+
assertInteger,
|
|
18
|
+
pitchClassOf
|
|
19
|
+
} from "./chunk-XEBG3G53.js";
|
|
18
20
|
|
|
19
|
-
// src/analyze/
|
|
20
|
-
function
|
|
21
|
-
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
|
+
}
|
|
22
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
|
+
);
|
|
172
|
+
}
|
|
173
|
+
function detectKeyBest(pitches, opts = {}) {
|
|
174
|
+
return detectKey(pitches, opts)[0] ?? null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/analyze/functional/internal.ts
|
|
23
178
|
function degreeRootPc(degreeNumber, key) {
|
|
24
179
|
const tones = scaleTonesInDegreeOrder(key);
|
|
25
180
|
if (tones.length === 0) {
|
|
26
|
-
return
|
|
181
|
+
return pitchClassOf(key.rootPc);
|
|
27
182
|
}
|
|
28
|
-
|
|
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
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
function romanReference(key) {
|
|
203
|
+
return scaleTonesInDegreeOrder(key).length === 7 ? key : majorKey(pitchClassOf(key.rootPc));
|
|
29
204
|
}
|
|
30
205
|
function isNeapolitan(chord, key) {
|
|
31
|
-
return
|
|
206
|
+
return pitchClassOf(chord.rootPc - key.rootPc) === 1 && chord.quality === "maj";
|
|
32
207
|
}
|
|
33
208
|
|
|
34
209
|
// src/analyze/functional/roman.ts
|
|
@@ -99,7 +274,17 @@ var ROMAN_STYLE = {
|
|
|
99
274
|
"7b13": { lower: false, suffix: "7b13" },
|
|
100
275
|
"11": { lower: false, suffix: "11" },
|
|
101
276
|
"13": { lower: false, suffix: "13" },
|
|
102
|
-
"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" }
|
|
103
288
|
};
|
|
104
289
|
var SUFFIX_QUALITY = new Map(
|
|
105
290
|
Object.entries(ROMAN_STYLE).map(
|
|
@@ -130,24 +315,26 @@ function parseInversion(figures) {
|
|
|
130
315
|
function parseSimpleRoman(text, key) {
|
|
131
316
|
const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());
|
|
132
317
|
if (!match) {
|
|
133
|
-
throw new
|
|
318
|
+
throw new InvalidInputError(`Invalid Roman numeral: ${text}`);
|
|
134
319
|
}
|
|
135
320
|
const accidental = match[1] === "b" ? -1 : match[1] === "#" ? 1 : 0;
|
|
136
321
|
const glyph = (match[2] ?? "").toUpperCase();
|
|
137
322
|
const degreeNumber = ROMAN_TO_DEGREE[glyph];
|
|
138
323
|
if (degreeNumber === void 0) {
|
|
139
|
-
throw new
|
|
324
|
+
throw new InvalidInputError(`Invalid Roman numeral: ${text}`);
|
|
140
325
|
}
|
|
141
326
|
const isUpper = (match[2] ?? "")[0] === (match[2] ?? "")[0]?.toUpperCase();
|
|
142
327
|
const suffix = match[3] ?? "";
|
|
143
|
-
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));
|
|
144
331
|
const canonical = SUFFIX_QUALITY.get(`${isUpper ? "u" : "l"}:${suffix}`);
|
|
145
332
|
if (canonical !== void 0) {
|
|
146
333
|
return { rootPc, quality: canonical, inversion: 0 };
|
|
147
334
|
}
|
|
148
335
|
const parsedSuffix = /^(o|°|dim|ø|\+|aug)?(maj7|M7)?(65|64|43|42|7|6|2)?$/.exec(suffix);
|
|
149
336
|
if (!parsedSuffix) {
|
|
150
|
-
throw new
|
|
337
|
+
throw new InvalidInputError(`Unsupported suffix "${suffix}" in Roman numeral: ${text}`);
|
|
151
338
|
}
|
|
152
339
|
const qualityMarker = parsedSuffix[1] ?? "";
|
|
153
340
|
const isDim = qualityMarker === "o" || qualityMarker === "\xB0" || qualityMarker === "dim";
|
|
@@ -156,7 +343,9 @@ function parseSimpleRoman(text, key) {
|
|
|
156
343
|
const explicitMaj7 = parsedSuffix[2] !== void 0;
|
|
157
344
|
const figures = parsedSuffix[3] ?? "";
|
|
158
345
|
if (figures !== "" && !INVERSION_FIGURES.has(figures)) {
|
|
159
|
-
throw new
|
|
346
|
+
throw new InvalidInputError(
|
|
347
|
+
`Unsupported figured-bass or extension "${figures}" in Roman numeral: ${text}`
|
|
348
|
+
);
|
|
160
349
|
}
|
|
161
350
|
const { inversion, seventh } = parseInversion(figures);
|
|
162
351
|
const hasSeventh = explicitMaj7 || seventh || isHalfDim;
|
|
@@ -185,7 +374,7 @@ function parseSimpleRoman(text, key) {
|
|
|
185
374
|
function chordFromParsed(parsed) {
|
|
186
375
|
const chord = makeChord(parsed.rootPc, parsed.quality);
|
|
187
376
|
if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {
|
|
188
|
-
chord.bassPc =
|
|
377
|
+
chord.bassPc = pitchClassOf(parsed.rootPc + (chord.intervals[parsed.inversion] ?? 0));
|
|
189
378
|
}
|
|
190
379
|
return chord;
|
|
191
380
|
}
|
|
@@ -205,26 +394,32 @@ function romanStyle(quality) {
|
|
|
205
394
|
return ROMAN_STYLE[quality];
|
|
206
395
|
}
|
|
207
396
|
function romanSpelling(rootPc, key) {
|
|
208
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
209
|
-
const diatonic = tones.indexOf(
|
|
397
|
+
const tones = scaleTonesInDegreeOrder(romanReference(key));
|
|
398
|
+
const diatonic = tones.indexOf(pitchClassOf(rootPc));
|
|
210
399
|
if (diatonic >= 0) {
|
|
211
400
|
return { degreeNumber: diatonic + 1, accidental: "" };
|
|
212
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
|
+
}
|
|
213
408
|
for (let i = 0; i < tones.length; i += 1) {
|
|
214
409
|
const degreeNumber2 = i + 1;
|
|
215
410
|
if (degreeNumber2 === 1 || degreeNumber2 === 5) {
|
|
216
411
|
continue;
|
|
217
412
|
}
|
|
218
|
-
if (
|
|
413
|
+
if (pitchClassOf((tones[i] ?? 0) - 1) === pitchClassOf(rootPc)) {
|
|
219
414
|
return { degreeNumber: degreeNumber2, accidental: "b" };
|
|
220
415
|
}
|
|
221
416
|
}
|
|
222
417
|
for (let i = 0; i < tones.length; i += 1) {
|
|
223
|
-
if (
|
|
418
|
+
if (pitchClassOf((tones[i] ?? 0) + 1) === pitchClassOf(rootPc)) {
|
|
224
419
|
return { degreeNumber: i + 1, accidental: "#" };
|
|
225
420
|
}
|
|
226
421
|
}
|
|
227
|
-
const [degreeNumber, accidental] = OFFSET_SPELLING[
|
|
422
|
+
const [degreeNumber, accidental] = OFFSET_SPELLING[pitchClassOf(rootPc - key.rootPc)] ?? [1, ""];
|
|
228
423
|
return { degreeNumber, accidental };
|
|
229
424
|
}
|
|
230
425
|
function baseMarker(quality) {
|
|
@@ -258,14 +453,65 @@ var SEVENTH_FIGURE_QUALITIES = /* @__PURE__ */ new Set([
|
|
|
258
453
|
"aug7",
|
|
259
454
|
"augMaj7"
|
|
260
455
|
]);
|
|
261
|
-
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
|
+
}
|
|
262
509
|
const { degreeNumber, accidental } = romanSpelling(chord.rootPc, key);
|
|
263
510
|
const { lower, suffix } = romanStyle(chord.quality);
|
|
264
|
-
const
|
|
265
|
-
const cased = lower ? numeral.toLowerCase() : numeral;
|
|
511
|
+
const cased = numeralFor(degreeNumber, lower);
|
|
266
512
|
let inversion = 0;
|
|
267
513
|
if (chord.bassPc !== void 0) {
|
|
268
|
-
const idx = chord.intervals.findIndex((iv) =>
|
|
514
|
+
const idx = chord.intervals.findIndex((iv) => pitchClassOf(chord.rootPc + iv) === chord.bassPc);
|
|
269
515
|
if (idx > 0) {
|
|
270
516
|
inversion = idx;
|
|
271
517
|
}
|
|
@@ -281,7 +527,7 @@ function chordToRoman(chord, key) {
|
|
|
281
527
|
|
|
282
528
|
// src/analyze/functional/borrowed.ts
|
|
283
529
|
function isHarmonicMinorAlteration(chord, key) {
|
|
284
|
-
return isMinorKey(key) && isDiatonic(chord, { rootPc:
|
|
530
|
+
return isMinorKey(key) && isDiatonic(chord, { rootPc: pitchClassOf(key.rootPc), modeMask12: HARMONIC_MINOR_MASK });
|
|
285
531
|
}
|
|
286
532
|
function isBorrowedChord(chord, key) {
|
|
287
533
|
if (isDiatonic(chord, key)) {
|
|
@@ -300,7 +546,7 @@ function borrowedSource(chord, key) {
|
|
|
300
546
|
return "neapolitan";
|
|
301
547
|
}
|
|
302
548
|
if (isBorrowedChord(chord, key)) {
|
|
303
|
-
return isMinorKey(key) ? "
|
|
549
|
+
return isMinorKey(key) ? "parallelMajor" : "parallelMinor";
|
|
304
550
|
}
|
|
305
551
|
return null;
|
|
306
552
|
}
|
|
@@ -338,14 +584,35 @@ function isMinorKey(key) {
|
|
|
338
584
|
return Boolean(hasMinorThird) && !hasMajorThird2;
|
|
339
585
|
}
|
|
340
586
|
function functionOf(chord, key) {
|
|
341
|
-
|
|
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);
|
|
342
609
|
}
|
|
343
610
|
function isDiatonic(chord, key) {
|
|
344
611
|
return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
|
|
345
612
|
}
|
|
346
613
|
function parallelKey(key) {
|
|
347
614
|
return {
|
|
348
|
-
rootPc:
|
|
615
|
+
rootPc: pitchClassOf(key.rootPc),
|
|
349
616
|
modeMask12: isMinorKey(key) ? MAJOR_MASK : NATURAL_MINOR_MASK
|
|
350
617
|
};
|
|
351
618
|
}
|
|
@@ -353,40 +620,28 @@ function isDiminishedQuality(quality) {
|
|
|
353
620
|
return quality === "dim" || quality === "dim7" || quality === "m7b5";
|
|
354
621
|
}
|
|
355
622
|
function hasMajorThird(chord) {
|
|
356
|
-
return chord.intervals.some((interval) =>
|
|
357
|
-
}
|
|
358
|
-
function qualityAwareFunction(chord, key) {
|
|
359
|
-
const offset = mod12(chord.rootPc - key.rootPc);
|
|
360
|
-
if (isDiminishedQuality(chord.quality) && (offset === 11 || offset === 6)) {
|
|
361
|
-
return "dominant";
|
|
362
|
-
}
|
|
363
|
-
if (isNeapolitan(chord, key)) {
|
|
364
|
-
return "subdominant";
|
|
365
|
-
}
|
|
366
|
-
if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {
|
|
367
|
-
return "subdominant";
|
|
368
|
-
}
|
|
369
|
-
return functionOf(chord, key);
|
|
623
|
+
return chord.intervals.some((interval) => pitchClassOf(interval) === 4);
|
|
370
624
|
}
|
|
371
625
|
function analyzeChord(chord, key) {
|
|
372
626
|
const source = borrowedSource(chord, key);
|
|
373
627
|
return {
|
|
374
|
-
function:
|
|
628
|
+
function: functionOf(chord, key),
|
|
375
629
|
borrowed: source !== null,
|
|
376
630
|
source,
|
|
377
631
|
roman: chordToRoman(chord, key)
|
|
378
632
|
};
|
|
379
633
|
}
|
|
380
634
|
function secondaryDominant(targetDegree, key) {
|
|
635
|
+
assertInteger(targetDegree, "targetDegree", 0, 6);
|
|
381
636
|
const targetRoot = degreeRootPc(targetDegree + 1, key);
|
|
382
|
-
return makeChord(
|
|
637
|
+
return makeChord(pitchClassOf(targetRoot + 7), "dom7");
|
|
383
638
|
}
|
|
384
639
|
|
|
385
640
|
// src/analyze/functional/cadence.ts
|
|
386
641
|
function detectCadence(from, to, key) {
|
|
387
|
-
const tonic =
|
|
388
|
-
const fromOffset =
|
|
389
|
-
const toOffset =
|
|
642
|
+
const tonic = pitchClassOf(key.rootPc);
|
|
643
|
+
const fromOffset = pitchClassOf(from.rootPc - tonic);
|
|
644
|
+
const toOffset = pitchClassOf(to.rootPc - tonic);
|
|
390
645
|
const deceptiveTargets = isMinorKey(key) ? [8] : [8, 9];
|
|
391
646
|
if (fromOffset === 7 && toOffset === 0) {
|
|
392
647
|
return "authentic";
|
|
@@ -403,134 +658,12 @@ function detectCadence(from, to, key) {
|
|
|
403
658
|
return null;
|
|
404
659
|
}
|
|
405
660
|
|
|
406
|
-
// src/analyze/detect/index.ts
|
|
407
|
-
function pitchClass(value) {
|
|
408
|
-
return (Math.trunc(value) % 12 + 12) % 12;
|
|
409
|
-
}
|
|
410
|
-
function uniquePitchClasses(pitches) {
|
|
411
|
-
return [...new Set(pitches.map(pitchClass))].sort((a, b) => a - b);
|
|
412
|
-
}
|
|
413
|
-
function assertPitches(pitches) {
|
|
414
|
-
assertGenerationBudget(pitches.length, "detection pitches");
|
|
415
|
-
for (let index = 0; index < pitches.length; index += 1) {
|
|
416
|
-
assertFiniteNumber(pitches[index] ?? Number.NaN, `pitches[${index}]`);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
function detectChord(pitches, opts = {}) {
|
|
420
|
-
assertPitches(pitches);
|
|
421
|
-
if (opts.bassPc !== void 0) assertFiniteNumber(opts.bassPc, "bassPc");
|
|
422
|
-
const input = uniquePitchClasses(pitches);
|
|
423
|
-
if (input.length === 0) {
|
|
424
|
-
return [];
|
|
425
|
-
}
|
|
426
|
-
const inputKind = opts.input === void 0 || opts.input === "auto" ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11) ? "pitchClass" : "midi" : opts.input;
|
|
427
|
-
const bassPc = opts.bassPc !== void 0 ? pitchClass(opts.bassPc) : inputKind === "midi" ? pitchClass(pitches.reduce((lowest, pitch) => Math.min(lowest, pitch), Infinity)) : void 0;
|
|
428
|
-
const inputSet = new Set(input);
|
|
429
|
-
const matches = [];
|
|
430
|
-
const qualities = chordQualities();
|
|
431
|
-
for (const rootPc of input) {
|
|
432
|
-
for (const quality of qualities) {
|
|
433
|
-
const chord = makeChord(rootPc, quality);
|
|
434
|
-
const tones = chordPitchClasses(chord);
|
|
435
|
-
const toneSet = new Set(tones);
|
|
436
|
-
const missingPcs = tones.filter((pc) => !inputSet.has(pc));
|
|
437
|
-
const fifthPc = pitchClass(rootPc + 7);
|
|
438
|
-
if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
|
-
const extraPcs = input.filter((pc) => !toneSet.has(pc));
|
|
442
|
-
const bassIndex = bassPc === void 0 ? -1 : chord.intervals.findIndex((iv) => pitchClass(rootPc + iv) === bassPc);
|
|
443
|
-
const inversion = bassPc === void 0 ? null : bassIndex > 0 ? bassIndex : 0;
|
|
444
|
-
const match = {
|
|
445
|
-
rootPc,
|
|
446
|
-
quality,
|
|
447
|
-
missingPcs,
|
|
448
|
-
extraPcs,
|
|
449
|
-
exact: extraPcs.length === 0 && missingPcs.length === 0,
|
|
450
|
-
inversion
|
|
451
|
-
};
|
|
452
|
-
if (bassPc !== void 0 && bassPc !== rootPc) {
|
|
453
|
-
match.bassPc = bassPc;
|
|
454
|
-
}
|
|
455
|
-
matches.push(match);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
matches.sort((a, b) => {
|
|
459
|
-
if (a.extraPcs.length !== b.extraPcs.length) {
|
|
460
|
-
return a.extraPcs.length - b.extraPcs.length;
|
|
461
|
-
}
|
|
462
|
-
if (a.missingPcs.length !== b.missingPcs.length) {
|
|
463
|
-
return a.missingPcs.length - b.missingPcs.length;
|
|
464
|
-
}
|
|
465
|
-
if (a.inversion === 0 !== (b.inversion === 0)) {
|
|
466
|
-
return a.inversion === 0 ? -1 : 1;
|
|
467
|
-
}
|
|
468
|
-
const aSize = chordPitchClasses(makeChord(a.rootPc, a.quality)).length;
|
|
469
|
-
const bSize = chordPitchClasses(makeChord(b.rootPc, b.quality)).length;
|
|
470
|
-
return bSize - aSize;
|
|
471
|
-
});
|
|
472
|
-
return matches;
|
|
473
|
-
}
|
|
474
|
-
function detectChordBest(pitches, opts = {}) {
|
|
475
|
-
const best = detectChord(pitches, opts)[0];
|
|
476
|
-
if (!best) {
|
|
477
|
-
return null;
|
|
478
|
-
}
|
|
479
|
-
return makeChord(best.rootPc, best.quality, best.bassPc);
|
|
480
|
-
}
|
|
481
|
-
var MINOR_MASK_VARIANTS = [NATURAL_MINOR_MASK, HARMONIC_MINOR_MASK, MELODIC_MINOR_MASK];
|
|
482
|
-
function detectKey(pitches) {
|
|
483
|
-
assertPitches(pitches);
|
|
484
|
-
const input = uniquePitchClasses(pitches);
|
|
485
|
-
if (input.length === 0) {
|
|
486
|
-
return [];
|
|
487
|
-
}
|
|
488
|
-
const counts = /* @__PURE__ */ new Map();
|
|
489
|
-
for (const pc of pitches.map(pitchClass)) {
|
|
490
|
-
counts.set(pc, (counts.get(pc) ?? 0) + 1);
|
|
491
|
-
}
|
|
492
|
-
const total = pitches.length;
|
|
493
|
-
const results = [];
|
|
494
|
-
for (let tonic = 0; tonic < 12; tonic += 1) {
|
|
495
|
-
for (const mode of ["major", "minor"]) {
|
|
496
|
-
const key = mode === "major" ? majorKey(tonic) : minorKey(tonic);
|
|
497
|
-
const masks = mode === "major" ? [MAJOR_MASK] : MINOR_MASK_VARIANTS;
|
|
498
|
-
let inScale = 0;
|
|
499
|
-
let weighted = Number.NEGATIVE_INFINITY;
|
|
500
|
-
for (const mask of masks) {
|
|
501
|
-
let variantInScale = 0;
|
|
502
|
-
let variantWeighted = 0;
|
|
503
|
-
for (const pc of input) {
|
|
504
|
-
const offset = (pc - tonic + 12) % 12;
|
|
505
|
-
if (mask >> offset & 1) {
|
|
506
|
-
variantInScale += 1;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
for (const [pc, count] of counts) {
|
|
510
|
-
const offset = (pc - tonic + 12) % 12;
|
|
511
|
-
if (mask >> offset & 1) {
|
|
512
|
-
variantWeighted += count;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
if (variantWeighted > weighted) {
|
|
516
|
-
weighted = variantWeighted;
|
|
517
|
-
inScale = variantInScale;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
weighted += (counts.get(tonic) ?? 0) * 0.5;
|
|
521
|
-
results.push({
|
|
522
|
-
key,
|
|
523
|
-
mode,
|
|
524
|
-
fit: inScale / input.length,
|
|
525
|
-
score: weighted / total
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
results.sort((a, b) => b.score - a.score);
|
|
530
|
-
return results.map(({ key, mode, fit }) => ({ key, mode, fit }));
|
|
531
|
-
}
|
|
532
|
-
|
|
533
661
|
export {
|
|
662
|
+
detectChord,
|
|
663
|
+
detectChordBest,
|
|
664
|
+
detectKey,
|
|
665
|
+
detectKeyFromNotes,
|
|
666
|
+
detectKeyBest,
|
|
534
667
|
romanToChord,
|
|
535
668
|
chordToRoman,
|
|
536
669
|
isMinorKey,
|
|
@@ -541,9 +674,6 @@ export {
|
|
|
541
674
|
secondaryDominant,
|
|
542
675
|
isBorrowedChord,
|
|
543
676
|
borrowedSource,
|
|
544
|
-
detectCadence
|
|
545
|
-
detectChord,
|
|
546
|
-
detectChordBest,
|
|
547
|
-
detectKey
|
|
677
|
+
detectCadence
|
|
548
678
|
};
|
|
549
|
-
//# sourceMappingURL=chunk-
|
|
679
|
+
//# sourceMappingURL=chunk-KMHOICIY.js.map
|