@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
|
@@ -4,24 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
var _chunkPI3UXRWKcjs = require('./chunk-PI3UXRWK.cjs');
|
|
8
9
|
|
|
9
10
|
// src/theory/scale/degrees.ts
|
|
10
11
|
function scaleOffset(pitch, key) {
|
|
11
|
-
return (
|
|
12
|
+
return (_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pitch) - _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, key.rootPc) + 12) % 12;
|
|
12
13
|
}
|
|
13
14
|
function isScaleTone(pitch, key) {
|
|
14
15
|
return (key.modeMask12 >> scaleOffset(pitch, key) & 1) === 1;
|
|
15
16
|
}
|
|
16
17
|
function nearestScaleTone(pitch, key) {
|
|
17
|
-
const base = Math.round(pitch);
|
|
18
|
+
const base = _chunkPI3UXRWKcjs.clampToMidi.call(void 0, Math.round(pitch), "pitch");
|
|
18
19
|
for (let distance = 0; distance < 12; distance += 1) {
|
|
19
20
|
const lower = base - distance;
|
|
20
|
-
if (isScaleTone(lower, key)) {
|
|
21
|
+
if (lower >= 0 && isScaleTone(lower, key)) {
|
|
21
22
|
return lower;
|
|
22
23
|
}
|
|
23
24
|
const higher = base + distance;
|
|
24
|
-
if (isScaleTone(higher, key)) {
|
|
25
|
+
if (higher <= 127 && isScaleTone(higher, key)) {
|
|
25
26
|
return higher;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -41,7 +42,7 @@ function pitchToScaleDegree(pitch, key) {
|
|
|
41
42
|
return degree;
|
|
42
43
|
}
|
|
43
44
|
function diatonicPitchClasses(key) {
|
|
44
|
-
const root =
|
|
45
|
+
const root = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, key.rootPc);
|
|
45
46
|
const pcs = [];
|
|
46
47
|
for (let n = 0; n < 12; n += 1) {
|
|
47
48
|
if ((key.modeMask12 >> n & 1) === 1) {
|
|
@@ -51,7 +52,7 @@ function diatonicPitchClasses(key) {
|
|
|
51
52
|
return pcs.sort((a, b) => a - b);
|
|
52
53
|
}
|
|
53
54
|
function scaleTonesInDegreeOrder(key) {
|
|
54
|
-
const root =
|
|
55
|
+
const root = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, key.rootPc);
|
|
55
56
|
const pcs = [];
|
|
56
57
|
for (let n = 0; n < 12; n += 1) {
|
|
57
58
|
if ((key.modeMask12 >> n & 1) === 1) {
|
|
@@ -64,7 +65,8 @@ function scaleTonesInDegreeOrder(key) {
|
|
|
64
65
|
// src/theory/scale/masks.ts
|
|
65
66
|
function maskFromOffsets(offsets) {
|
|
66
67
|
let mask = 1;
|
|
67
|
-
for (const offset of offsets) {
|
|
68
|
+
for (const [index, offset] of offsets.entries()) {
|
|
69
|
+
_chunkPI3UXRWKcjs.assertInteger.call(void 0, offset, `offsets[${index}]`);
|
|
68
70
|
mask |= 1 << (offset % 12 + 12) % 12;
|
|
69
71
|
}
|
|
70
72
|
return mask;
|
|
@@ -121,20 +123,20 @@ function namedScaleMask(name) {
|
|
|
121
123
|
function requireScaleMask(name, label = "scale") {
|
|
122
124
|
const mask = namedScaleMask(name);
|
|
123
125
|
if (mask === void 0) {
|
|
124
|
-
throw new (0,
|
|
126
|
+
throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(`Unknown ${label}: ${String(name)}`);
|
|
125
127
|
}
|
|
126
128
|
return mask;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
// src/theory/scale/key.ts
|
|
130
132
|
function majorKey(rootPc) {
|
|
131
|
-
return { rootPc: (
|
|
133
|
+
return { rootPc: _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, rootPc), modeMask12: MAJOR_MASK };
|
|
132
134
|
}
|
|
133
135
|
function minorKey(rootPc) {
|
|
134
|
-
return { rootPc: (
|
|
136
|
+
return { rootPc: _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, rootPc), modeMask12: NATURAL_MINOR_MASK };
|
|
135
137
|
}
|
|
136
138
|
function scaleByName(name, rootPc) {
|
|
137
|
-
return { rootPc: (
|
|
139
|
+
return { rootPc: _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, rootPc), modeMask12: requireScaleMask(name) };
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
// src/theory/chord/index.ts
|
|
@@ -150,6 +152,9 @@ var QUALITY_INTERVALS = {
|
|
|
150
152
|
dim7: [0, 3, 6, 9],
|
|
151
153
|
m7b5: [0, 3, 6, 10],
|
|
152
154
|
minMaj7: [0, 3, 7, 11],
|
|
155
|
+
minMaj9: [0, 3, 7, 11, 14],
|
|
156
|
+
minMaj11: [0, 3, 7, 11, 14, 17],
|
|
157
|
+
minMaj13: [0, 3, 7, 11, 14, 21],
|
|
153
158
|
aug7: [0, 4, 8, 10],
|
|
154
159
|
augMaj7: [0, 4, 8, 11],
|
|
155
160
|
"6": [0, 4, 7, 9],
|
|
@@ -187,74 +192,74 @@ function chordQualities() {
|
|
|
187
192
|
return Object.keys(QUALITY_INTERVALS);
|
|
188
193
|
}
|
|
189
194
|
function chordFromDegree(degree, ext, key) {
|
|
190
|
-
|
|
195
|
+
_chunkPI3UXRWKcjs.assertInteger.call(void 0, degree, "chord degree", -1e3, 1e3);
|
|
191
196
|
if (!Object.hasOwn(QUALITY_INTERVALS, ext)) {
|
|
192
|
-
throw new (0,
|
|
197
|
+
throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(`Unknown chord quality: ${String(ext)}`);
|
|
193
198
|
}
|
|
194
199
|
const tones = scaleTonesInDegreeOrder(key);
|
|
195
200
|
const length = tones.length;
|
|
196
201
|
const index = length > 0 ? (degree % length + length) % length : 0;
|
|
197
|
-
const rootPc = _nullishCoalesce(tones[index], () => (
|
|
202
|
+
const rootPc = _nullishCoalesce(tones[index], () => ( _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, key.rootPc)));
|
|
198
203
|
return { rootPc, quality: ext, intervals: [...QUALITY_INTERVALS[ext]] };
|
|
199
204
|
}
|
|
200
205
|
function makeChord(rootPc, quality, bassPc) {
|
|
201
|
-
|
|
206
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, rootPc, "chord rootPc");
|
|
202
207
|
if (bassPc !== void 0) {
|
|
203
|
-
|
|
208
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, bassPc, "chord bassPc");
|
|
204
209
|
}
|
|
205
210
|
if (!Object.hasOwn(QUALITY_INTERVALS, quality)) {
|
|
206
|
-
throw new (0,
|
|
211
|
+
throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(`Unknown chord quality: ${String(quality)}`);
|
|
207
212
|
}
|
|
208
213
|
const chord = {
|
|
209
|
-
rootPc:
|
|
214
|
+
rootPc: _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, rootPc),
|
|
210
215
|
quality,
|
|
211
216
|
intervals: [..._nullishCoalesce(QUALITY_INTERVALS[quality], () => ( []))]
|
|
212
217
|
};
|
|
213
218
|
if (bassPc !== void 0) {
|
|
214
|
-
chord.bassPc =
|
|
219
|
+
chord.bassPc = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, bassPc);
|
|
215
220
|
}
|
|
216
221
|
return chord;
|
|
217
222
|
}
|
|
218
223
|
function transposeChord(chord, semitones) {
|
|
219
|
-
|
|
224
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, semitones, "semitones");
|
|
220
225
|
const steps = Math.round(semitones);
|
|
221
226
|
const moved = {
|
|
222
|
-
rootPc:
|
|
227
|
+
rootPc: _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.rootPc + steps),
|
|
223
228
|
quality: chord.quality,
|
|
224
229
|
intervals: [...chord.intervals]
|
|
225
230
|
};
|
|
226
231
|
if (chord.bassPc !== void 0) {
|
|
227
|
-
moved.bassPc =
|
|
232
|
+
moved.bassPc = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.bassPc + steps);
|
|
228
233
|
}
|
|
229
234
|
return moved;
|
|
230
235
|
}
|
|
231
236
|
function chordPitchClasses(chord, opts = {}) {
|
|
232
237
|
const set = /* @__PURE__ */ new Set();
|
|
233
238
|
for (const interval of chord.intervals) {
|
|
234
|
-
set.add(
|
|
239
|
+
set.add(_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.rootPc + interval));
|
|
235
240
|
}
|
|
236
241
|
if (opts.includeBass !== false && chord.bassPc !== void 0) {
|
|
237
|
-
set.add(
|
|
242
|
+
set.add(_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.bassPc));
|
|
238
243
|
}
|
|
239
244
|
return [...set].sort((a, b) => a - b);
|
|
240
245
|
}
|
|
241
246
|
function intervalAboveRoot(pitch, chord) {
|
|
242
|
-
return ((
|
|
247
|
+
return ((_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pitch) - _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.rootPc)) % 12 + 12) % 12;
|
|
243
248
|
}
|
|
244
249
|
function isChordMember(pitch, chord) {
|
|
245
|
-
return chord === null ? false : chordPitchClasses(chord).includes(
|
|
250
|
+
return chord === null ? false : chordPitchClasses(chord).includes(_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pitch));
|
|
246
251
|
}
|
|
247
252
|
function chordToneOffsets(chord) {
|
|
248
253
|
return new Set(chord.intervals.map((i) => (i % 12 + 12) % 12));
|
|
249
254
|
}
|
|
250
255
|
function chordToneRole(pitch, chord) {
|
|
251
|
-
const interval = (
|
|
256
|
+
const interval = (_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pitch) - _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.rootPc) + 12) % 12;
|
|
252
257
|
const tones = chordToneOffsets(chord);
|
|
253
258
|
if (interval === 0) {
|
|
254
259
|
return "root";
|
|
255
260
|
}
|
|
256
261
|
if (interval === 3 || interval === 4) {
|
|
257
|
-
return tones.has(interval) ? "third" : null;
|
|
262
|
+
return tones.has(interval) && (interval !== 3 || !tones.has(4)) ? "third" : null;
|
|
258
263
|
}
|
|
259
264
|
if (interval === 7) {
|
|
260
265
|
return tones.has(7) ? "fifth" : null;
|
|
@@ -268,7 +273,7 @@ function chordToneRole(pitch, chord) {
|
|
|
268
273
|
if (interval === 9) {
|
|
269
274
|
const hasHigherSeventh = tones.has(10) || tones.has(11);
|
|
270
275
|
const isDiminishedSeventh = tones.has(3) && tones.has(6) && !hasHigherSeventh;
|
|
271
|
-
if (isDiminishedSeventh) {
|
|
276
|
+
if (tones.has(9) && isDiminishedSeventh) {
|
|
272
277
|
return "seventh";
|
|
273
278
|
}
|
|
274
279
|
if (tones.has(9) && !hasHigherSeventh) {
|
|
@@ -315,21 +320,29 @@ function classifySeventh(thirdIc, fifthIc, seventhIc) {
|
|
|
315
320
|
if (fifthIc === 8 && seventhIc === 11) {
|
|
316
321
|
return "augMaj7";
|
|
317
322
|
}
|
|
323
|
+
if (fifthIc === 6 && seventhIc === 10) {
|
|
324
|
+
return "7b5";
|
|
325
|
+
}
|
|
318
326
|
if (seventhIc === 11) {
|
|
319
327
|
return "maj7";
|
|
320
328
|
}
|
|
321
329
|
return "dom7";
|
|
322
330
|
}
|
|
331
|
+
function matchesQualityIntervals(quality, intervals) {
|
|
332
|
+
const template = QUALITY_INTERVALS[quality];
|
|
333
|
+
return template.length === intervals.length && template.every((interval, index) => interval === intervals[index]);
|
|
334
|
+
}
|
|
323
335
|
function stackThirds(degree, key, size) {
|
|
336
|
+
_chunkPI3UXRWKcjs.assertInteger.call(void 0, degree, "chord degree", -1e3, 1e3);
|
|
324
337
|
const tones = scaleTonesInDegreeOrder(key);
|
|
325
338
|
const length = tones.length;
|
|
326
339
|
if (length !== 7) {
|
|
327
|
-
throw new (0,
|
|
340
|
+
throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
|
|
328
341
|
`diatonic chord stacking requires a heptatonic scale (received ${length} tones)`
|
|
329
342
|
);
|
|
330
343
|
}
|
|
331
344
|
const idx = (step) => ((degree + step) % length + length) % length;
|
|
332
|
-
const rootPc = _nullishCoalesce(tones[idx(0)], () => (
|
|
345
|
+
const rootPc = _nullishCoalesce(tones[idx(0)], () => ( _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, key.rootPc)));
|
|
333
346
|
const offsets = [0, 2, 4, 6].slice(0, size).map((step) => {
|
|
334
347
|
const pc = _nullishCoalesce(tones[idx(step)], () => ( rootPc));
|
|
335
348
|
return ((pc - rootPc) % 12 + 12) % 12;
|
|
@@ -337,6 +350,11 @@ function stackThirds(degree, key, size) {
|
|
|
337
350
|
const thirdIc = _nullishCoalesce(offsets[1], () => ( 4));
|
|
338
351
|
const fifthIc = _nullishCoalesce(offsets[2], () => ( 7));
|
|
339
352
|
const quality = size === 3 ? classifyTriad(thirdIc, fifthIc) : classifySeventh(thirdIc, fifthIc, _nullishCoalesce(offsets[3], () => ( 11)));
|
|
353
|
+
if (!matchesQualityIntervals(quality, offsets)) {
|
|
354
|
+
throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
|
|
355
|
+
`scale thirds at degree ${degree} produce unsupported intervals [${offsets.join(", ")}]`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
340
358
|
return { rootPc, quality, intervals: offsets };
|
|
341
359
|
}
|
|
342
360
|
function diatonicTriad(degree, key) {
|
|
@@ -351,14 +369,14 @@ function createsVoiceCrossing(upperPitch, lowerPitch) {
|
|
|
351
369
|
return upperPitch < lowerPitch;
|
|
352
370
|
}
|
|
353
371
|
function createsVerticalDissonance(a, b, twoVoice) {
|
|
354
|
-
return !
|
|
372
|
+
return !_chunkPI3UXRWKcjs.isConsonantInterval.call(void 0, a - b, twoVoice);
|
|
355
373
|
}
|
|
356
374
|
function isForbiddenMelodicLeap(prev, cur) {
|
|
357
375
|
const semis = Math.abs(cur - prev);
|
|
358
376
|
return semis === 6 || semis === 10 || semis === 11 || semis > 12;
|
|
359
377
|
}
|
|
360
378
|
function simpleClass(semitones) {
|
|
361
|
-
return Math.abs(semitones)
|
|
379
|
+
return _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, Math.abs(semitones));
|
|
362
380
|
}
|
|
363
381
|
function isPerfectClass(cls) {
|
|
364
382
|
return cls === 0 || cls === 7;
|
|
@@ -413,9 +431,9 @@ function exceedsSpacing(upperPitch, lowerPitch, maxSemitones = 12) {
|
|
|
413
431
|
return Math.abs(upperPitch - lowerPitch) > maxSemitones;
|
|
414
432
|
}
|
|
415
433
|
function isLeadingToneResolution(prev, cur, key) {
|
|
416
|
-
const tonic =
|
|
434
|
+
const tonic = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, key.rootPc);
|
|
417
435
|
const leading = (tonic + 11) % 12;
|
|
418
|
-
if (
|
|
436
|
+
if (_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, prev) !== leading || _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, cur) !== tonic) {
|
|
419
437
|
return false;
|
|
420
438
|
}
|
|
421
439
|
return cur > prev && cur - prev <= 2;
|
|
@@ -476,4 +494,4 @@ function isLeadingToneResolution(prev, cur, key) {
|
|
|
476
494
|
|
|
477
495
|
|
|
478
496
|
exports.isScaleTone = isScaleTone; exports.nearestScaleTone = nearestScaleTone; exports.pitchToScaleDegree = pitchToScaleDegree; exports.diatonicPitchClasses = diatonicPitchClasses; exports.scaleTonesInDegreeOrder = scaleTonesInDegreeOrder; exports.maskFromOffsets = maskFromOffsets; exports.MAJOR_MASK = MAJOR_MASK; exports.NATURAL_MINOR_MASK = NATURAL_MINOR_MASK; exports.HARMONIC_MINOR_MASK = HARMONIC_MINOR_MASK; exports.MELODIC_MINOR_MASK = MELODIC_MINOR_MASK; exports.DORIAN_MASK = DORIAN_MASK; exports.PHRYGIAN_MASK = PHRYGIAN_MASK; exports.LYDIAN_MASK = LYDIAN_MASK; exports.MIXOLYDIAN_MASK = MIXOLYDIAN_MASK; exports.LOCRIAN_MASK = LOCRIAN_MASK; exports.MAJOR_PENTATONIC_MASK = MAJOR_PENTATONIC_MASK; exports.MINOR_PENTATONIC_MASK = MINOR_PENTATONIC_MASK; exports.BLUES_MASK = BLUES_MASK; exports.WHOLE_TONE_MASK = WHOLE_TONE_MASK; exports.OCTATONIC_HALF_WHOLE_MASK = OCTATONIC_HALF_WHOLE_MASK; exports.OCTATONIC_WHOLE_HALF_MASK = OCTATONIC_WHOLE_HALF_MASK; exports.CHROMATIC_MASK = CHROMATIC_MASK; exports.LYDIAN_DOMINANT_MASK = LYDIAN_DOMINANT_MASK; exports.MIXOLYDIAN_B13_MASK = MIXOLYDIAN_B13_MASK; exports.LOCRIAN_NATURAL2_MASK = LOCRIAN_NATURAL2_MASK; exports.ALTERED_MASK = ALTERED_MASK; exports.PHRYGIAN_DOMINANT_MASK = PHRYGIAN_DOMINANT_MASK; exports.NAMED_SCALES = NAMED_SCALES; exports.namedScaleMask = namedScaleMask; exports.requireScaleMask = requireScaleMask; exports.majorKey = majorKey; exports.minorKey = minorKey; exports.scaleByName = scaleByName; exports.chordQualities = chordQualities; exports.chordFromDegree = chordFromDegree; exports.makeChord = makeChord; exports.transposeChord = transposeChord; exports.chordPitchClasses = chordPitchClasses; exports.intervalAboveRoot = intervalAboveRoot; exports.isChordMember = isChordMember; exports.chordToneRole = chordToneRole; exports.diatonicTriad = diatonicTriad; exports.diatonicSeventh = diatonicSeventh; exports.createsVoiceCrossing = createsVoiceCrossing; exports.createsVerticalDissonance = createsVerticalDissonance; exports.isForbiddenMelodicLeap = isForbiddenMelodicLeap; exports.createsParallelPerfect = createsParallelPerfect; exports.createsParallelOctave = createsParallelOctave; exports.createsParallelUnison = createsParallelUnison; exports.createsHiddenParallelPerfect = createsHiddenParallelPerfect; exports.createsVoiceOverlap = createsVoiceOverlap; exports.exceedsSpacing = exceedsSpacing; exports.isLeadingToneResolution = isLeadingToneResolution;
|
|
479
|
-
//# sourceMappingURL=chunk-
|
|
497
|
+
//# sourceMappingURL=chunk-BSLTLGM3.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-BSLTLGM3.cjs","../src/theory/scale/degrees.ts","../src/theory/scale/masks.ts","../src/theory/scale/key.ts","../src/theory/chord/index.ts","../src/theory/counterpoint/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-BSLTLGM3.cjs","sourcesContent":[null,"import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport { clampToMidi } from '../../core/validation/index.js';\n\n/** Scale offset of a pitch relative to the key root, in [0, 11]. */\nfunction scaleOffset(pitch: number, key: KeyScale): number {\n return (pitchClass(pitch) - pitchClass(key.rootPc) + 12) % 12;\n}\n\n/**\n * Test whether a pitch belongs to the scale.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param key The key/scale to test against.\n * @returns True if the pitch class is a member of the scale.\n *\n * @example\n * ```ts\n * import { majorKey, isScaleTone } from '@libraz/libcantus';\n * const key = majorKey(0); // C major\n * isScaleTone(64, key); // true — E is in C major\n * isScaleTone(61, key); // false — C# is not\n * ```\n *\n * @category Scales\n */\nexport function isScaleTone(pitch: number, key: KeyScale): boolean {\n return ((key.modeMask12 >> scaleOffset(pitch, key)) & 1) === 1;\n}\n\n/**\n * Find the nearest MIDI pitch whose pitch class is in the scale.\n *\n * The search expands symmetrically outward from `pitch`. On a tie (equal\n * distance above and below) the lower pitch is returned.\n *\n * @param pitch MIDI pitch to snap.\n * @param key The key/scale to snap to.\n * @returns The nearest in-scale MIDI pitch.\n *\n * @category Scales\n */\nexport function nearestScaleTone(pitch: number, key: KeyScale): number {\n const base = clampToMidi(Math.round(pitch), 'pitch');\n for (let distance = 0; distance < 12; distance += 1) {\n const lower = base - distance;\n if (lower >= 0 && isScaleTone(lower, key)) {\n return lower;\n }\n const higher = base + distance;\n if (higher <= 127 && isScaleTone(higher, key)) {\n return higher;\n }\n }\n return base;\n}\n\n/**\n * Get the 0-based scale degree of a pitch.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param key The key/scale to measure against.\n * @returns The 0-based degree, or -1 if the pitch is not in the scale.\n *\n * @category Scales\n */\nexport function pitchToScaleDegree(pitch: number, key: KeyScale): number {\n const offset = scaleOffset(pitch, key);\n if (((key.modeMask12 >> offset) & 1) === 0) {\n return -1;\n }\n let degree = 0;\n for (let n = 0; n < offset; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n degree += 1;\n }\n }\n return degree;\n}\n\n/**\n * List the pitch classes of a key's scale.\n *\n * @param key The key/scale to enumerate.\n * @returns The member pitch classes, sorted ascending in [0, 11].\n *\n * @category Scales\n */\nexport function diatonicPitchClasses(key: KeyScale): number[] {\n const root = pitchClass(key.rootPc);\n const pcs: number[] = [];\n for (let n = 0; n < 12; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n pcs.push((root + n) % 12);\n }\n }\n return pcs.sort((a, b) => a - b);\n}\n\n/**\n * List the pitch classes of a key's scale in ascending scale-degree order.\n *\n * Degree 0 is the root; degrees follow the mask bits in offset order rather\n * than sorted pitch-class order.\n *\n * @param key The key/scale to enumerate.\n * @returns The member pitch classes ordered by scale degree.\n *\n * @category Scales\n */\nexport function scaleTonesInDegreeOrder(key: KeyScale): number[] {\n const root = pitchClass(key.rootPc);\n const pcs: number[] = [];\n for (let n = 0; n < 12; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n pcs.push((root + n) % 12);\n }\n }\n return pcs;\n}\n","import { InvalidInputError } from '../../core/errors/index.js';\nimport { assertInteger } from '../../core/validation/index.js';\n\n/**\n * Build a 12-bit mode mask from a list of semitone offsets above the root.\n *\n * Bit 0 (the root) is always set, enforcing the `KeyScale` invariant that the\n * root is a scale tone even when the offset list omits 0.\n *\n * @category Scales\n */\nexport function maskFromOffsets(offsets: readonly number[]): number {\n let mask = 1;\n for (const [index, offset] of offsets.entries()) {\n assertInteger(offset, `offsets[${index}]`);\n mask |= 1 << (((offset % 12) + 12) % 12);\n }\n return mask;\n}\n\n/**\n * Mode mask for the major (Ionian) scale: offsets {0, 2, 4, 5, 7, 9, 11}.\n *\n * @category Scales\n */\nexport const MAJOR_MASK = 0b101010110101;\n\n/**\n * Mode mask for the natural minor (Aeolian) scale: offsets {0, 2, 3, 5, 7, 8, 10}.\n *\n * @category Scales\n */\nexport const NATURAL_MINOR_MASK = 0b010110101101;\n\n/**\n * Harmonic minor: natural minor with a raised seventh — offsets {0,2,3,5,7,8,11}.\n *\n * @category Scales\n */\nexport const HARMONIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 8, 11]);\n\n/**\n * Ascending melodic minor: offsets {0,2,3,5,7,9,11}.\n *\n * @category Scales\n */\nexport const MELODIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 11]);\n\n/**\n * Dorian mode: offsets {0,2,3,5,7,9,10}.\n *\n * @category Scales\n */\nexport const DORIAN_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 10]);\n\n/**\n * Phrygian mode: offsets {0,1,3,5,7,8,10}.\n *\n * @category Scales\n */\nexport const PHRYGIAN_MASK = maskFromOffsets([0, 1, 3, 5, 7, 8, 10]);\n\n/**\n * Lydian mode: offsets {0,2,4,6,7,9,11}.\n *\n * @category Scales\n */\nexport const LYDIAN_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 11]);\n\n/**\n * Mixolydian mode: offsets {0,2,4,5,7,9,10}.\n *\n * @category Scales\n */\nexport const MIXOLYDIAN_MASK = maskFromOffsets([0, 2, 4, 5, 7, 9, 10]);\n\n/**\n * Locrian mode: offsets {0,1,3,5,6,8,10}.\n *\n * @category Scales\n */\nexport const LOCRIAN_MASK = maskFromOffsets([0, 1, 3, 5, 6, 8, 10]);\n\n/**\n * Major pentatonic: offsets {0,2,4,7,9}.\n *\n * @category Scales\n */\nexport const MAJOR_PENTATONIC_MASK = maskFromOffsets([0, 2, 4, 7, 9]);\n\n/**\n * Minor pentatonic: offsets {0,3,5,7,10}.\n *\n * @category Scales\n */\nexport const MINOR_PENTATONIC_MASK = maskFromOffsets([0, 3, 5, 7, 10]);\n\n/**\n * Hexatonic blues scale: minor pentatonic plus the flat-fifth — {0,3,5,6,7,10}.\n *\n * @category Scales\n */\nexport const BLUES_MASK = maskFromOffsets([0, 3, 5, 6, 7, 10]);\n\n/**\n * Whole-tone scale: offsets {0,2,4,6,8,10}.\n *\n * @category Scales\n */\nexport const WHOLE_TONE_MASK = maskFromOffsets([0, 2, 4, 6, 8, 10]);\n\n/**\n * Octatonic (half-step first): offsets {0,1,3,4,6,7,9,10}.\n *\n * @category Scales\n */\nexport const OCTATONIC_HALF_WHOLE_MASK = maskFromOffsets([0, 1, 3, 4, 6, 7, 9, 10]);\n\n/**\n * Octatonic (whole-step first): offsets {0,2,3,5,6,8,9,11}.\n *\n * @category Scales\n */\nexport const OCTATONIC_WHOLE_HALF_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 9, 11]);\n\n/**\n * Chromatic scale: all twelve pitch classes.\n *\n * @category Scales\n */\nexport const CHROMATIC_MASK = 0b111111111111;\n\n/**\n * Lydian dominant (melodic minor's fourth mode): offsets {0,2,4,6,7,9,10}.\n *\n * The scale over an unaltered dominant with a raised eleventh.\n *\n * @category Scales\n */\nexport const LYDIAN_DOMINANT_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 10]);\n\n/**\n * Mixolydian b13, the aeolian dominant (melodic minor's fifth mode): offsets\n * {0,2,4,5,7,8,10}.\n *\n * The scale over a dominant with a flat thirteenth.\n *\n * @category Scales\n */\nexport const MIXOLYDIAN_B13_MASK = maskFromOffsets([0, 2, 4, 5, 7, 8, 10]);\n\n/**\n * Locrian natural 2 (melodic minor's sixth mode): offsets {0,2,3,5,6,8,10}.\n *\n * The scale over a half-diminished chord used as a minor ii.\n *\n * @category Scales\n */\nexport const LOCRIAN_NATURAL2_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 10]);\n\n/**\n * The altered scale, or superlocrian (melodic minor's seventh mode): offsets\n * {0,1,3,4,6,8,10}.\n *\n * The scale over an altered dominant: every tension raised or lowered.\n *\n * @category Scales\n */\nexport const ALTERED_MASK = maskFromOffsets([0, 1, 3, 4, 6, 8, 10]);\n\n/**\n * Phrygian dominant (harmonic minor's fifth mode): offsets {0,1,4,5,7,8,10}.\n *\n * The scale over the dominant of a minor key, and the characteristic sound of\n * the flamenco and maqam repertoires.\n *\n * @category Scales\n */\nexport const PHRYGIAN_DOMINANT_MASK = maskFromOffsets([0, 1, 4, 5, 7, 8, 10]);\n\n/**\n * Named scale masks addressable by {@link scaleByName}.\n *\n * @category Scales\n */\nexport const NAMED_SCALES = Object.freeze({\n major: MAJOR_MASK,\n ionian: MAJOR_MASK,\n naturalMinor: NATURAL_MINOR_MASK,\n aeolian: NATURAL_MINOR_MASK,\n harmonicMinor: HARMONIC_MINOR_MASK,\n melodicMinor: MELODIC_MINOR_MASK,\n dorian: DORIAN_MASK,\n phrygian: PHRYGIAN_MASK,\n lydian: LYDIAN_MASK,\n mixolydian: MIXOLYDIAN_MASK,\n locrian: LOCRIAN_MASK,\n lydianDominant: LYDIAN_DOMINANT_MASK,\n mixolydianB13: MIXOLYDIAN_B13_MASK,\n locrianNatural2: LOCRIAN_NATURAL2_MASK,\n altered: ALTERED_MASK,\n phrygianDominant: PHRYGIAN_DOMINANT_MASK,\n majorPentatonic: MAJOR_PENTATONIC_MASK,\n minorPentatonic: MINOR_PENTATONIC_MASK,\n blues: BLUES_MASK,\n wholeTone: WHOLE_TONE_MASK,\n octatonicHalfWhole: OCTATONIC_HALF_WHOLE_MASK,\n octatonicWholeHalf: OCTATONIC_WHOLE_HALF_MASK,\n chromatic: CHROMATIC_MASK,\n});\n\n/**\n * The name of a built-in scale, as accepted by {@link scaleByName},\n * {@link avoidNotes}, and {@link availableTensions}.\n *\n * @category Scales\n */\nexport type ScaleName = keyof typeof NAMED_SCALES;\n\n/**\n * A scale name that completes to the built-in names but still accepts any\n * string, so a caller with a name from configuration is not forced to cast.\n *\n * @category Scales\n */\nexport type ScaleNameInput = ScaleName | (string & {});\n\n/**\n * The mask of a named scale, or undefined when the name is not one.\n *\n * Looking the name up through this helper is what keeps `'constructor'` or\n * `'toString'` from resolving to something inherited from `Object.prototype`\n * and producing a scale whose mask is a function.\n *\n * @param name The scale name.\n * @returns The 12-bit mask, or undefined for an unknown name.\n * @category Scales\n */\nexport function namedScaleMask(name: ScaleNameInput): number | undefined {\n return Object.hasOwn(NAMED_SCALES, name) ? NAMED_SCALES[name as ScaleName] : undefined;\n}\n\n/**\n * The mask of a named scale, rejecting an unknown name.\n *\n * Every public entry point that takes a scale name funnels through here, so a\n * typo is reported the same way everywhere instead of being answered with an\n * empty result that also means \"this scale has none\".\n *\n * @param name The scale name.\n * @param label What the name is, for the error message.\n * @returns The 12-bit mask.\n * @throws If the name is not a built-in scale.\n * @category Scales\n */\nexport function requireScaleMask(name: ScaleNameInput, label = 'scale'): number {\n const mask = namedScaleMask(name);\n if (mask === undefined) {\n throw new InvalidInputError(`Unknown ${label}: ${String(name)}`);\n }\n return mask;\n}\n","import { pitchClassOf } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport type { ScaleNameInput } from './masks.js';\nimport { MAJOR_MASK, NATURAL_MINOR_MASK, requireScaleMask } from './masks.js';\n\n/**\n * Build a `KeyScale` for a major key on the given root pitch class.\n *\n * @example\n * ```ts\n * import { majorKey } from '@libraz/libcantus';\n * const cMajor = majorKey(0); // C major: { rootPc: 0, modeMask12: MAJOR_MASK }\n * ```\n *\n * @category Scales\n */\nexport function majorKey(rootPc: number): KeyScale {\n return { rootPc: pitchClassOf(rootPc), modeMask12: MAJOR_MASK };\n}\n\n/**\n * Build a `KeyScale` for a natural-minor key on the given root pitch class.\n *\n * @example\n * ```ts\n * import { minorKey } from '@libraz/libcantus';\n * const aMinor = minorKey(9); // A natural minor: { rootPc: 9, modeMask12: NATURAL_MINOR_MASK }\n * ```\n *\n * @category Scales\n */\nexport function minorKey(rootPc: number): KeyScale {\n return { rootPc: pitchClassOf(rootPc), modeMask12: NATURAL_MINOR_MASK };\n}\n\n/**\n * Build a `KeyScale` from a named scale (see {@link NAMED_SCALES}).\n *\n * @param name The scale name, e.g. `'dorian'` or `'harmonicMinor'`.\n * @param rootPc The root pitch class.\n * @returns The key/scale.\n * @throws If the name is not a known scale.\n *\n * @example\n * ```ts\n * import { scaleByName } from '@libraz/libcantus';\n * const dDorian = scaleByName('dorian', 2); // D Dorian, rootPc 2\n * ```\n *\n * @category Scales\n */\nexport function scaleByName(name: ScaleNameInput, rootPc: number): KeyScale {\n return { rootPc: pitchClassOf(rootPc), modeMask12: requireScaleMask(name) };\n}\n","import { InvalidInputError } from '../../core/errors/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport { assertFiniteNumber, assertInteger } from '../../core/validation/index.js';\nimport { scaleTonesInDegreeOrder } from '../scale/index.js';\n\n/**\n * Chord quality identifiers understood by the chord builder.\n *\n * @category Chords\n */\nexport type ChordQuality =\n | 'maj'\n | 'min'\n | 'dim'\n | 'aug'\n | 'maj7'\n | 'min7'\n | 'dom7'\n | 'dim7'\n | 'm7b5'\n | 'minMaj7'\n | 'minMaj9'\n | 'minMaj11'\n | 'minMaj13'\n | 'aug7'\n | 'augMaj7'\n | 'majb5'\n | '6'\n | 'min6'\n | '6/9'\n | 'sus2'\n | 'sus4'\n | 'add9'\n | 'add11'\n | 'maj9'\n | 'min9'\n | 'dom9'\n | '7b9'\n | '7#9'\n | '7#11'\n | '7b13'\n | '11'\n | '13'\n | '5'\n | '7sus4'\n | '7b5'\n | '7alt'\n | '13b9'\n | 'maj13'\n | 'maj7#11'\n | 'min11'\n | 'min13'\n | 'minAdd9'\n | 'min6/9';\n\n/**\n * The basic role a chord tone plays: the degrees a chord's own template names.\n *\n * The shared vocabulary behind {@link chordToneRole}, the harmony layer's\n * `HarmonyRole`, and the voice analyser's labels, so the three cannot drift.\n *\n * @category Chords\n */\nexport type ChordToneRole = 'root' | 'third' | 'fifth' | 'sixth' | 'seventh';\n\n/**\n * A bare spelled pitch used as an enharmonic hint: a diatonic letter\n * (0..6 = C..B) plus a chromatic alteration (-2 double-flat .. +2 double-sharp).\n * Mirrors the letter/alter half of the pitch module's `Note` without an octave.\n *\n * @category Chords\n */\nexport type PitchSpelling = {\n letter: number;\n alter: number;\n};\n\n/**\n * A chord expressed as a root pitch class plus semitone offsets.\n *\n * `rootSpelling`/`bassSpelling` are optional enharmonic hints recorded by\n * parsers (e.g. `parseChordSymbol('Bb7')`) so formatters can reproduce the\n * original spelling instead of defaulting to sharps. Consumers may ignore\n * them; a hint is only trusted when its pitch class still matches the\n * corresponding `rootPc`/`bassPc`.\n *\n * @category Chords\n */\nexport type Chord = {\n rootPc: number;\n quality: ChordQuality;\n intervals: number[];\n bassPc?: number;\n rootSpelling?: PitchSpelling;\n bassSpelling?: PitchSpelling;\n};\n\n/**\n * A chord positioned in time: a root pitch class and quality placed at a beat,\n * with optional harmonic-analysis hints. Serves as the neutral exchange record\n * between progression generators and timeline analysers.\n *\n * @category Composition\n */\nexport type ChordSpan = {\n rootPc: number;\n quality: ChordQuality;\n startBeat: number;\n bassPc?: number;\n /** 0-based scale degree of the chord root, when known. */\n degree?: number;\n /** True when the chord is a secondary dominant tonicizing another degree. */\n secondaryDominant?: boolean;\n};\n\n/**\n * A chord occupying a half-open beat span `[startBeat, endBeat)`.\n *\n * The exchange record between anything that lays chords out in time — a\n * timeline analysis, a bass-line plan, an accompaniment generator — so those\n * surfaces share one definition instead of agreeing by coincidence.\n *\n * @category Composition\n */\nexport type ChordSegment = {\n startBeat: number;\n endBeat: number;\n chord: Chord;\n};\n\n/** Semitone offsets from the root for each supported chord quality. */\nconst QUALITY_INTERVALS: Record<ChordQuality, number[]> = {\n maj: [0, 4, 7],\n min: [0, 3, 7],\n dim: [0, 3, 6],\n aug: [0, 4, 8],\n majb5: [0, 4, 6],\n maj7: [0, 4, 7, 11],\n min7: [0, 3, 7, 10],\n dom7: [0, 4, 7, 10],\n dim7: [0, 3, 6, 9],\n m7b5: [0, 3, 6, 10],\n minMaj7: [0, 3, 7, 11],\n minMaj9: [0, 3, 7, 11, 14],\n minMaj11: [0, 3, 7, 11, 14, 17],\n minMaj13: [0, 3, 7, 11, 14, 21],\n aug7: [0, 4, 8, 10],\n augMaj7: [0, 4, 8, 11],\n '6': [0, 4, 7, 9],\n min6: [0, 3, 7, 9],\n '6/9': [0, 4, 7, 9, 14],\n sus2: [0, 2, 7],\n sus4: [0, 5, 7],\n add9: [0, 4, 7, 14],\n add11: [0, 4, 7, 17],\n maj9: [0, 4, 7, 11, 14],\n min9: [0, 3, 7, 10, 14],\n dom9: [0, 4, 7, 10, 14],\n '7b9': [0, 4, 7, 10, 13],\n '7#9': [0, 4, 7, 10, 15],\n '7#11': [0, 4, 7, 10, 18],\n '7b13': [0, 4, 7, 10, 20],\n '11': [0, 7, 10, 14, 17],\n '13': [0, 4, 7, 10, 14, 21],\n '5': [0, 7],\n '7sus4': [0, 5, 7, 10],\n '7b5': [0, 4, 6, 10],\n // The altered dominant as it is voiced from a lead sheet: a dominant seventh\n // with a raised fifth and a raised ninth. The full seven-note altered stack\n // has no seven-letter spelling, so it is not what this symbol denotes.\n '7alt': [0, 4, 8, 10, 15],\n '13b9': [0, 4, 7, 10, 13, 21],\n maj13: [0, 4, 7, 11, 14, 21],\n 'maj7#11': [0, 4, 7, 11, 18],\n min11: [0, 3, 7, 10, 14, 17],\n min13: [0, 3, 7, 10, 14, 21],\n minAdd9: [0, 3, 7, 14],\n 'min6/9': [0, 3, 7, 9, 14],\n};\n\n/**\n * All chord qualities the builder understands, in declaration order.\n *\n * The order is stable but carries no meaning: it is neither alphabetical nor\n * sorted by template size. {@link detectChord} uses it as its final tie-break,\n * so it is part of the observable behaviour and does not get reshuffled\n * casually — but do not read it as a ranking.\n *\n * @category Chords\n */\nexport function chordQualities(): ChordQuality[] {\n return Object.keys(QUALITY_INTERVALS) as ChordQuality[];\n}\n\n/**\n * Build a chord rooted on a diatonic scale degree.\n *\n * The root pitch class is the degree's diatonic pitch class in `key`; the\n * quality's interval template is attached unchanged. Degrees beyond the scale\n * length wrap around.\n *\n * @param degree 0-based scale degree of the chord root.\n * @param ext Chord quality to apply.\n * @param key Key context supplying the diatonic root.\n * @returns The constructed chord.\n *\n * @category Chords\n */\nexport function chordFromDegree(degree: number, ext: ChordQuality, key: KeyScale): Chord {\n assertInteger(degree, 'chord degree', -1000, 1000);\n if (!Object.hasOwn(QUALITY_INTERVALS, ext)) {\n throw new InvalidInputError(`Unknown chord quality: ${String(ext)}`);\n }\n const tones = scaleTonesInDegreeOrder(key);\n const length = tones.length;\n const index = length > 0 ? ((degree % length) + length) % length : 0;\n const rootPc = tones[index] ?? pitchClass(key.rootPc);\n return { rootPc, quality: ext, intervals: [...QUALITY_INTERVALS[ext]] };\n}\n\n/**\n * Build a chord from an explicit root pitch class and quality.\n *\n * @param rootPc Root pitch class (0..11).\n * @param quality Chord quality supplying the interval template.\n * @param bassPc Optional slash-chord bass pitch class.\n * @returns The constructed chord.\n *\n * @example\n * ```ts\n * import { makeChord } from '@libraz/libcantus';\n * const cmaj7 = makeChord(0, 'maj7');\n * // { rootPc: 0, quality: 'maj7', intervals: [0, 4, 7, 11] }\n * ```\n *\n * @category Chords\n */\nexport function makeChord(rootPc: number, quality: ChordQuality, bassPc?: number): Chord {\n // A NaN root formats as 'C' and an unknown quality as 'Cundefined', so both\n // travel on into voicing, detection, and serialization as a chord that looks\n // real. They are rejected where the chord is built instead.\n assertFiniteNumber(rootPc, 'chord rootPc');\n if (bassPc !== undefined) {\n assertFiniteNumber(bassPc, 'chord bassPc');\n }\n if (!Object.hasOwn(QUALITY_INTERVALS, quality)) {\n throw new InvalidInputError(`Unknown chord quality: ${String(quality)}`);\n }\n const chord: Chord = {\n rootPc: pitchClass(rootPc),\n quality,\n intervals: [...(QUALITY_INTERVALS[quality] ?? [])],\n };\n if (bassPc !== undefined) {\n chord.bassPc = pitchClass(bassPc);\n }\n return chord;\n}\n\n/**\n * Transpose a chord by a number of semitones.\n *\n * The interval template is carried over untouched, so a chord whose intervals\n * were customised keeps them, and a slash bass moves with the root. Enharmonic\n * spelling hints are dropped rather than shifted: the letter a transposed root\n * should take depends on the destination key, which a semitone count does not\n * carry. Pass the result through {@link spellChord} to respell it.\n *\n * @param chord The chord to transpose.\n * @param semitones The signed semitone offset.\n * @returns The transposed chord.\n * @throws If `semitones` is not finite.\n *\n * @example\n * ```ts\n * import { formatChordSymbol, parseChordSymbol, transposeChord } from '@libraz/libcantus';\n * formatChordSymbol(transposeChord(parseChordSymbol('C/G'), 2)); // 'D/A'\n * ```\n *\n * @category Chords\n */\nexport function transposeChord(chord: Chord, semitones: number): Chord {\n assertFiniteNumber(semitones, 'semitones');\n const steps = Math.round(semitones);\n const moved: Chord = {\n rootPc: pitchClass(chord.rootPc + steps),\n quality: chord.quality,\n intervals: [...chord.intervals],\n };\n if (chord.bassPc !== undefined) {\n moved.bassPc = pitchClass(chord.bassPc + steps);\n }\n return moved;\n}\n\n/**\n * Get the sorted, deduplicated pitch classes of a chord.\n *\n * A slash bass is one of them: `F/G` sounds a G, the voicers put it in the\n * bass, and a set that omitted it would not re-detect as the chord it came\n * from. Pass `includeBass: false` to enumerate the interval template alone.\n *\n * @param chord The chord to enumerate.\n * @param opts Set `includeBass: false` to omit a slash bass.\n * @returns The chord's pitch classes, sorted ascending in [0, 11].\n *\n * @example\n * ```ts\n * import { makeChord, chordPitchClasses } from '@libraz/libcantus';\n * chordPitchClasses(makeChord(0, 'maj7')); // [0, 4, 7, 11]\n * chordPitchClasses(makeChord(5, 'maj', 7)); // [0, 5, 7, 9] — F/G, G included\n * ```\n *\n * @category Chords\n */\nexport function chordPitchClasses(chord: Chord, opts: { includeBass?: boolean } = {}): number[] {\n const set = new Set<number>();\n for (const interval of chord.intervals) {\n set.add(pitchClass(chord.rootPc + interval));\n }\n if (opts.includeBass !== false && chord.bassPc !== undefined) {\n set.add(pitchClass(chord.bassPc));\n }\n return [...set].sort((a, b) => a - b);\n}\n\n/**\n * A pitch's interval class above the chord root, in [0, 11].\n *\n * The shared reading of \"where does this pitch sit in the chord\", used by the\n * safety evaluator and the voice analyser alike so they cannot disagree.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param chord The chord providing the root reference.\n * @returns The interval class above the root.\n * @category Chords\n */\nexport function intervalAboveRoot(pitch: number, chord: Chord): number {\n return (((pitchClass(pitch) - pitchClass(chord.rootPc)) % 12) + 12) % 12;\n}\n\n/**\n * Whether a pitch is one of a chord's tones, ignoring octave.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param chord The chord, or null for no sounding harmony.\n * @returns True when the pitch class belongs to the chord.\n * @category Chords\n */\nexport function isChordMember(pitch: number, chord: Chord | null): boolean {\n return chord === null ? false : chordPitchClasses(chord).includes(pitchClass(pitch));\n}\n\n/** The chord-tone offsets present in a chord, reduced to pitch-class space. */\nfunction chordToneOffsets(chord: Chord): Set<number> {\n return new Set(chord.intervals.map((i) => ((i % 12) + 12) % 12));\n}\n\n/**\n * Determine a pitch's harmonic role within a chord.\n *\n * The role is derived from the pitch's interval above the chord root, reduced\n * modulo 12. A major sixth (9) reads as a `sixth` for sixth chords but as a\n * `seventh` for a diminished-seventh chord; ninths and other tensions have no\n * basic role and return null.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param chord The chord providing the root reference.\n * @returns The chord-tone role, or null if the pitch has no basic role.\n *\n * @category Chords\n */\nexport function chordToneRole(pitch: number, chord: Chord): ChordToneRole | null {\n const interval = (pitchClass(pitch) - pitchClass(chord.rootPc) + 12) % 12;\n const tones = chordToneOffsets(chord);\n if (interval === 0) {\n return 'root';\n }\n if (interval === 3 || interval === 4) {\n return tones.has(interval) && (interval !== 3 || !tones.has(4)) ? 'third' : null;\n }\n if (interval === 7) {\n return tones.has(7) ? 'fifth' : null;\n }\n if (interval === 6) {\n // A diminished fifth is the chord's fifth only when no perfect fifth is\n // present; alongside a perfect fifth it is a #11 tension, not a fifth.\n return tones.has(6) && !tones.has(7) ? 'fifth' : null;\n }\n if (interval === 8) {\n // An augmented fifth is the chord's fifth only without a perfect fifth;\n // alongside a perfect fifth it is a b13 tension, not a fifth.\n return tones.has(8) && !tones.has(7) ? 'fifth' : null;\n }\n if (interval === 9) {\n const hasHigherSeventh = tones.has(10) || tones.has(11);\n const isDiminishedSeventh = tones.has(3) && tones.has(6) && !hasHigherSeventh;\n if (tones.has(9) && isDiminishedSeventh) {\n return 'seventh';\n }\n if (tones.has(9) && !hasHigherSeventh) {\n return 'sixth';\n }\n return null;\n }\n if (interval === 10 || interval === 11) {\n return tones.has(interval) ? 'seventh' : null;\n }\n return null;\n}\n\n/** Classify a stacked-thirds triad into a chord quality from its interval set. */\nfunction classifyTriad(thirdIc: number, fifthIc: number): ChordQuality {\n if (thirdIc === 4 && fifthIc === 8) {\n return 'aug';\n }\n if (thirdIc === 4 && fifthIc === 6) {\n return 'majb5';\n }\n if (thirdIc === 3 && fifthIc === 6) {\n return 'dim';\n }\n if (thirdIc === 3) {\n return 'min';\n }\n return 'maj';\n}\n\n/** Classify a stacked-thirds seventh chord into a chord quality. */\nfunction classifySeventh(thirdIc: number, fifthIc: number, seventhIc: number): ChordQuality {\n if (thirdIc === 3 && fifthIc === 6 && seventhIc === 9) {\n return 'dim7';\n }\n if (thirdIc === 3 && fifthIc === 6 && seventhIc === 10) {\n return 'm7b5';\n }\n if (thirdIc === 3 && seventhIc === 11) {\n return 'minMaj7';\n }\n if (thirdIc === 3) {\n return 'min7';\n }\n if (fifthIc === 8 && seventhIc === 10) {\n return 'aug7';\n }\n if (fifthIc === 8 && seventhIc === 11) {\n return 'augMaj7';\n }\n if (fifthIc === 6 && seventhIc === 10) {\n return '7b5';\n }\n if (seventhIc === 11) {\n return 'maj7';\n }\n return 'dom7';\n}\n\n/** Whether a classified quality describes precisely the stacked pitch classes. */\nfunction matchesQualityIntervals(quality: ChordQuality, intervals: readonly number[]): boolean {\n const template = QUALITY_INTERVALS[quality];\n return (\n template.length === intervals.length &&\n template.every((interval, index) => interval === intervals[index])\n );\n}\n\n/** Stack scale thirds from a scale degree into a chord of `size` notes. */\nfunction stackThirds(degree: number, key: KeyScale, size: 3 | 4): Chord {\n assertInteger(degree, 'chord degree', -1000, 1000);\n const tones = scaleTonesInDegreeOrder(key);\n const length = tones.length;\n if (length !== 7) {\n throw new InvalidInputError(\n `diatonic chord stacking requires a heptatonic scale (received ${length} tones)`,\n );\n }\n const idx = (step: number) => (((degree + step) % length) + length) % length;\n const rootPc = tones[idx(0)] ?? pitchClass(key.rootPc);\n const offsets = [0, 2, 4, 6].slice(0, size).map((step) => {\n const pc = tones[idx(step)] ?? rootPc;\n return (((pc - rootPc) % 12) + 12) % 12;\n });\n const thirdIc = offsets[1] ?? 4;\n const fifthIc = offsets[2] ?? 7;\n const quality =\n size === 3\n ? classifyTriad(thirdIc, fifthIc)\n : classifySeventh(thirdIc, fifthIc, offsets[3] ?? 11);\n if (!matchesQualityIntervals(quality, offsets)) {\n throw new InvalidInputError(\n `scale thirds at degree ${degree} produce unsupported intervals [${offsets.join(', ')}]`,\n );\n }\n return { rootPc, quality, intervals: offsets };\n}\n\n/**\n * Build the diatonic triad rooted on a scale degree by stacking scale thirds.\n *\n * Unlike {@link chordFromDegree}, the chord quality is derived from the scale\n * rather than supplied, so degrees yield their scale-correct triads (e.g. a\n * diminished triad on the leading tone of a major key).\n *\n * @param degree 0-based scale degree of the chord root.\n * @param key Key/scale context.\n * @returns The diatonic triad.\n * @throws If the scale is not heptatonic. Stacking thirds means skipping every\n * other degree, which only lands on a triad when there are seven of them, so\n * the pentatonic, blues, whole-tone, octatonic and chromatic scales have no\n * diatonic triad; use {@link chordFromDegree} with an explicit quality there.\n *\n * @example\n * ```ts\n * import { majorKey, diatonicTriad } from '@libraz/libcantus';\n * const tonic = diatonicTriad(0, majorKey(0)); // C major triad (degree 0 of C major)\n * ```\n *\n * @category Chords\n */\nexport function diatonicTriad(degree: number, key: KeyScale): Chord {\n return stackThirds(degree, key, 3);\n}\n\n/**\n * Build the diatonic seventh chord rooted on a scale degree.\n *\n * @param degree 0-based scale degree of the chord root.\n * @param key Key/scale context.\n * @returns The diatonic seventh chord.\n * @throws If the scale is not heptatonic, for the reason given on\n * {@link diatonicTriad}.\n *\n * @category Chords\n */\nexport function diatonicSeventh(degree: number, key: KeyScale): Chord {\n return stackThirds(degree, key, 4);\n}\n","import { isConsonantInterval } from '../../core/interval/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\n\n/**\n * Whether an upper voice has crossed below a lower voice.\n *\n * @param upperPitch Pitch of the nominally higher voice.\n * @param lowerPitch Pitch of the nominally lower voice.\n * @returns True when the upper voice sits below the lower voice.\n * @category Voicing & Counterpoint\n */\nexport function createsVoiceCrossing(upperPitch: number, lowerPitch: number): boolean {\n return upperPitch < lowerPitch;\n}\n\n/**\n * Whether two simultaneous pitches form a dissonance.\n *\n * @param a First pitch.\n * @param b Second pitch.\n * @param twoVoice When true, the perfect fourth counts as dissonant.\n * @returns True when the vertical interval is dissonant.\n * @category Voicing & Counterpoint\n */\nexport function createsVerticalDissonance(a: number, b: number, twoVoice: boolean): boolean {\n return !isConsonantInterval(a - b, twoVoice);\n}\n\n/**\n * Whether a melodic move is a forbidden leap: a tritone, either seventh (minor\n * or major), or any leap wider than an octave.\n *\n * Operates on the raw absolute distance, so compound leaps are not reduced to\n * their simple class before the octave check.\n *\n * @param prev Starting pitch.\n * @param cur Ending pitch.\n * @returns True when the leap is forbidden in strict counterpoint.\n * @category Voicing & Counterpoint\n */\nexport function isForbiddenMelodicLeap(prev: number, cur: number): boolean {\n const semis = Math.abs(cur - prev);\n return semis === 6 || semis === 10 || semis === 11 || semis > 12;\n}\n\n/** Reduce an interval to its simple class in [0, 11]. */\nfunction simpleClass(semitones: number): number {\n return pitchClass(Math.abs(semitones));\n}\n\n/** Whether a simple interval class is a perfect kind (unison/octave or fifth). */\nfunction isPerfectClass(cls: number): boolean {\n return cls === 0 || cls === 7;\n}\n\n/** Whether two voices move in the same direction (similar or parallel motion). */\nfunction similarMotion(aMove: number, bMove: number): boolean {\n return aMove !== 0 && bMove !== 0 && aMove > 0 === bMove > 0;\n}\n\n/** Whether both voices actually move (neither is stationary — excludes oblique motion). */\nfunction bothVoicesMove(aMove: number, bMove: number): boolean {\n return aMove !== 0 && bMove !== 0;\n}\n\n/**\n * Whether two voices move into consecutive perfect intervals of the same kind\n * (fifth-to-fifth, octave-to-octave, unison-to-unison).\n *\n * Both true parallels (similar motion) and anti-parallels — the same perfect\n * class reached by contrary motion, e.g. octave to octave with the voices moving\n * in opposite directions — are flagged, as both are forbidden in strict two-voice\n * counterpoint. The rule requires that both voices actually move: oblique motion\n * (either voice stationary) and the no-change case (identical pitches) are excluded.\n *\n * A fifth expanding to a twelfth counts (same perfect class); a fifth moving to\n * an octave does not (different perfect kinds — the direct/hidden case owned by\n * {@link createsHiddenParallelPerfect}).\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelPerfect(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (!bothVoicesMove(aCur - aPrev, bCur - bPrev)) {\n return false;\n }\n const nowClass = simpleClass(aCur - bCur);\n const prevClass = simpleClass(aPrev - bPrev);\n return isPerfectClass(nowClass) && nowClass === prevClass;\n}\n\n/**\n * Whether two voices move in consecutive parallel octaves (or unisons) by\n * similar motion.\n *\n * This is a strict subset of {@link createsParallelPerfect}: a similar-motion\n * octave-to-octave is the perfect-class-zero case that predicate already flags\n * (and it additionally catches the contrary-motion anti-parallel). Callers that\n * tally parallel violations should therefore use {@link createsParallelPerfect}\n * alone to avoid double counting; this predicate remains for callers wanting a\n * dedicated similar-motion octave test.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelOctave(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (!similarMotion(aCur - aPrev, bCur - bPrev)) {\n return false;\n }\n return simpleClass(aCur - bCur) === 0 && simpleClass(aPrev - bPrev) === 0;\n}\n\n/**\n * Whether two voices move in consecutive parallel unisons — both landing on the\n * same pitch, having shared a pitch on the previous move.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelUnison(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (aCur === aPrev || bCur === bPrev) {\n return false;\n }\n return aCur === bCur && aPrev === bPrev;\n}\n\n/**\n * Whether two voices reach a perfect interval by similar motion from an\n * imperfect one (a hidden/direct fifth or octave).\n *\n * The traditional step exception is applied: the approach is allowed when the\n * upper of the two voices moves by step, so only leaps into the perfect\n * interval are flagged.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsHiddenParallelPerfect(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n const aMove = aCur - aPrev;\n const bMove = bCur - bPrev;\n if (!similarMotion(aMove, bMove)) {\n return false;\n }\n const nowClass = simpleClass(aCur - bCur);\n const prevClass = simpleClass(aPrev - bPrev);\n // Approaching the same perfect class (e.g. fifth to fifth) is a true parallel\n // owned by createsParallelPerfect; approaching a different perfect interval\n // (fifth to octave, or vice versa) is the hidden/direct case flagged here.\n if (!isPerfectClass(nowClass) || prevClass === nowClass) {\n return false;\n }\n const upperMove = aCur >= bCur ? aMove : bMove;\n if (Math.abs(upperMove) <= 2) {\n return false; // upper voice moves by step — direct interval is acceptable\n }\n return true;\n}\n\n/**\n * Whether two voices overlap: the upper voice descends below where the lower\n * voice just was, or the lower voice rises above where the upper voice just was.\n * Distinct from a simultaneous voice crossing.\n *\n * @param upperPrev Previous pitch of the upper voice.\n * @param upperCur Current pitch of the upper voice.\n * @param lowerPrev Previous pitch of the lower voice.\n * @param lowerCur Current pitch of the lower voice.\n * @category Voicing & Counterpoint\n */\nexport function createsVoiceOverlap(\n upperPrev: number,\n upperCur: number,\n lowerPrev: number,\n lowerCur: number,\n): boolean {\n return upperCur < lowerPrev || lowerCur > upperPrev;\n}\n\n/**\n * Whether two adjacent upper voices are spaced more than a maximum apart\n * (commonly an octave). Bass-to-tenor spacing is conventionally exempt, so this\n * is meant for the upper voice pairs.\n *\n * @param upperPitch Pitch of the higher voice.\n * @param lowerPitch Pitch of the lower voice.\n * @param maxSemitones Maximum allowed spacing in semitones (default an octave).\n * @category Voicing & Counterpoint\n */\nexport function exceedsSpacing(upperPitch: number, lowerPitch: number, maxSemitones = 12): boolean {\n return Math.abs(upperPitch - lowerPitch) > maxSemitones;\n}\n\n/**\n * Whether a leading tone resolves correctly upward to the tonic.\n *\n * @param prev The leading-tone pitch.\n * @param cur The following pitch.\n * @param key Key context supplying the tonic.\n * @returns True when `prev` is the leading tone and `cur` is the tonic a step above.\n * @category Voicing & Counterpoint\n */\nexport function isLeadingToneResolution(prev: number, cur: number, key: KeyScale): boolean {\n const tonic = pitchClass(key.rootPc);\n const leading = (tonic + 11) % 12;\n if (pitchClass(prev) !== leading || pitchClass(cur) !== tonic) {\n return false;\n }\n return cur > prev && cur - prev <= 2;\n}\n"]}
|
|
@@ -4,35 +4,35 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunkPI3UXRWKcjs = require('./chunk-PI3UXRWK.cjs');
|
|
8
8
|
|
|
9
9
|
// src/core/tuning/index.ts
|
|
10
10
|
var TWELVE_TET = Object.freeze({ refStep: 69, refFreq: 440, divisions: 12 });
|
|
11
11
|
function assertTuning(tuning) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, tuning.refStep, "tuning.refStep");
|
|
13
|
+
_chunkPI3UXRWKcjs.assertRange.call(void 0, tuning.refFreq, Number.MIN_VALUE, Number.MAX_VALUE, "tuning.refFreq");
|
|
14
|
+
_chunkPI3UXRWKcjs.assertPositiveInt.call(void 0, tuning.divisions, "tuning.divisions");
|
|
15
15
|
return tuning;
|
|
16
16
|
}
|
|
17
17
|
function edo(n, refFreq = 440, refStep = 69) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
_chunkPI3UXRWKcjs.assertPositiveInt.call(void 0, n, "EDO divisions");
|
|
19
|
+
_chunkPI3UXRWKcjs.assertRange.call(void 0, refFreq, Number.MIN_VALUE, Number.MAX_VALUE, "reference frequency");
|
|
20
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, refStep, "reference step");
|
|
21
21
|
return { refStep, refFreq, divisions: n };
|
|
22
22
|
}
|
|
23
23
|
function frequencyOf(step, tuning = TWELVE_TET) {
|
|
24
|
-
|
|
24
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, step, "step");
|
|
25
25
|
assertTuning(tuning);
|
|
26
26
|
const result = tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);
|
|
27
|
-
return
|
|
27
|
+
return _chunkPI3UXRWKcjs.assertRange.call(void 0, result, Number.MIN_VALUE, Number.MAX_VALUE, "frequency result");
|
|
28
28
|
}
|
|
29
29
|
function nearestStep(freq, tuning = TWELVE_TET) {
|
|
30
|
-
|
|
30
|
+
_chunkPI3UXRWKcjs.assertRange.call(void 0, freq, Number.MIN_VALUE, Number.MAX_VALUE, "frequency");
|
|
31
31
|
assertTuning(tuning);
|
|
32
32
|
return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));
|
|
33
33
|
}
|
|
34
34
|
function stepOf(freq, tuning = TWELVE_TET) {
|
|
35
|
-
|
|
35
|
+
_chunkPI3UXRWKcjs.assertRange.call(void 0, freq, Number.MIN_VALUE, Number.MAX_VALUE, "frequency");
|
|
36
36
|
assertTuning(tuning);
|
|
37
37
|
return tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq);
|
|
38
38
|
}
|
|
@@ -41,49 +41,52 @@ function centsFromNearestStep(freq, tuning = TWELVE_TET) {
|
|
|
41
41
|
return centsOfSteps(exact - Math.round(exact), tuning);
|
|
42
42
|
}
|
|
43
43
|
function stepsOfCents(cents, tuning = TWELVE_TET) {
|
|
44
|
-
|
|
44
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, cents, "cents");
|
|
45
45
|
assertTuning(tuning);
|
|
46
|
-
return
|
|
46
|
+
return _chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, cents * tuning.divisions / 1200, "steps result");
|
|
47
47
|
}
|
|
48
48
|
function centsToRatio(cents) {
|
|
49
|
-
|
|
49
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, cents, "cents");
|
|
50
50
|
return 2 ** (cents / 1200);
|
|
51
51
|
}
|
|
52
52
|
function centsBetweenFreq(a, b) {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
_chunkPI3UXRWKcjs.assertRange.call(void 0, a, Number.MIN_VALUE, Number.MAX_VALUE, "first frequency");
|
|
54
|
+
_chunkPI3UXRWKcjs.assertRange.call(void 0, b, Number.MIN_VALUE, Number.MAX_VALUE, "second frequency");
|
|
55
55
|
return 1200 * (Math.log2(b) - Math.log2(a));
|
|
56
56
|
}
|
|
57
57
|
function centsOfSteps(steps, tuning = TWELVE_TET) {
|
|
58
|
-
|
|
58
|
+
_chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, steps, "steps");
|
|
59
59
|
assertTuning(tuning);
|
|
60
|
-
return
|
|
60
|
+
return _chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, steps * 1200 / tuning.divisions, "cents result");
|
|
61
61
|
}
|
|
62
62
|
function ratioToCents(numerator, denominator) {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
_chunkPI3UXRWKcjs.assertRange.call(void 0, numerator, Number.MIN_VALUE, Number.MAX_VALUE, "ratio numerator");
|
|
64
|
+
_chunkPI3UXRWKcjs.assertRange.call(void 0, denominator, Number.MIN_VALUE, Number.MAX_VALUE, "ratio denominator");
|
|
65
65
|
return 1200 * (Math.log2(numerator) - Math.log2(denominator));
|
|
66
66
|
}
|
|
67
|
+
function justRatio(numerator, denominator) {
|
|
68
|
+
return Object.freeze([numerator, denominator]);
|
|
69
|
+
}
|
|
67
70
|
var JUST_RATIOS = Object.freeze({
|
|
68
|
-
0:
|
|
69
|
-
1:
|
|
70
|
-
2:
|
|
71
|
-
3:
|
|
72
|
-
4:
|
|
73
|
-
5:
|
|
74
|
-
6:
|
|
75
|
-
7:
|
|
76
|
-
8:
|
|
77
|
-
9:
|
|
78
|
-
10:
|
|
79
|
-
11:
|
|
80
|
-
12:
|
|
71
|
+
0: justRatio(1, 1),
|
|
72
|
+
1: justRatio(16, 15),
|
|
73
|
+
2: justRatio(9, 8),
|
|
74
|
+
3: justRatio(6, 5),
|
|
75
|
+
4: justRatio(5, 4),
|
|
76
|
+
5: justRatio(4, 3),
|
|
77
|
+
6: justRatio(45, 32),
|
|
78
|
+
7: justRatio(3, 2),
|
|
79
|
+
8: justRatio(8, 5),
|
|
80
|
+
9: justRatio(5, 3),
|
|
81
|
+
10: justRatio(9, 5),
|
|
82
|
+
11: justRatio(15, 8),
|
|
83
|
+
12: justRatio(2, 1)
|
|
81
84
|
});
|
|
82
85
|
function justDeviationCents(semitoneClass) {
|
|
83
|
-
|
|
86
|
+
_chunkPI3UXRWKcjs.assertInteger.call(void 0, semitoneClass, "semitone class", 0, 12);
|
|
84
87
|
const ratio = JUST_RATIOS[semitoneClass];
|
|
85
88
|
if (!ratio) {
|
|
86
|
-
throw new (0,
|
|
89
|
+
throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(`semitone class has no just ratio: ${semitoneClass}`);
|
|
87
90
|
}
|
|
88
91
|
return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;
|
|
89
92
|
}
|
|
@@ -103,4 +106,4 @@ function justDeviationCents(semitoneClass) {
|
|
|
103
106
|
|
|
104
107
|
|
|
105
108
|
exports.TWELVE_TET = TWELVE_TET; exports.edo = edo; exports.frequencyOf = frequencyOf; exports.nearestStep = nearestStep; exports.stepOf = stepOf; exports.centsFromNearestStep = centsFromNearestStep; exports.stepsOfCents = stepsOfCents; exports.centsToRatio = centsToRatio; exports.centsBetweenFreq = centsBetweenFreq; exports.centsOfSteps = centsOfSteps; exports.ratioToCents = ratioToCents; exports.JUST_RATIOS = JUST_RATIOS; exports.justDeviationCents = justDeviationCents;
|
|
106
|
-
//# sourceMappingURL=chunk-
|
|
109
|
+
//# sourceMappingURL=chunk-CU6VG2YM.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-CU6VG2YM.cjs","../src/core/tuning/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-CU6VG2YM.cjs","sourcesContent":[null,"/**\n * Tuning and microtonality: frequency conversion, cents, arbitrary equal\n * temperaments (EDO), and just-intonation ratios.\n *\n * The rest of the library reasons in twelve-tone pitch classes, which is a\n * deliberate scope choice for tonal theory. This module is the escape hatch for\n * anyone who needs actual frequencies, cents, non-12 equal temperaments, or the\n * acoustic (just) tuning behind the tempered intervals.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport {\n assertFiniteNumber,\n assertInteger,\n assertPositiveInt,\n assertRange,\n} from '../validation/index.js';\n\n/**\n * An equal-tempered tuning: a reference pitch and a number of equal divisions of\n * the octave. With `divisions === 12` a step index is an ordinary MIDI number.\n *\n * @category Pitch & Intervals\n */\nexport type Tuning = {\n /** Step index (MIDI number when `divisions` is 12) whose frequency is `refFreq`. */\n readonly refStep: number;\n /** Frequency in Hz of `refStep`. */\n readonly refFreq: number;\n /** Equal divisions of the octave (12 standard; e.g. 19 or 31 for microtonal). */\n readonly divisions: number;\n};\n\n/**\n * Standard twelve-tone equal temperament, A4 (MIDI 69) = 440 Hz.\n *\n * @category Pitch & Intervals\n */\nexport const TWELVE_TET: Tuning = Object.freeze({ refStep: 69, refFreq: 440, divisions: 12 });\n\nfunction assertTuning(tuning: Tuning): Tuning {\n assertFiniteNumber(tuning.refStep, 'tuning.refStep');\n assertRange(tuning.refFreq, Number.MIN_VALUE, Number.MAX_VALUE, 'tuning.refFreq');\n assertPositiveInt(tuning.divisions, 'tuning.divisions');\n return tuning;\n}\n\n/**\n * Build an equal temperament with `n` divisions of the octave.\n *\n * @param n Divisions of the octave (e.g. 19, 24, 31).\n * @param refFreq Reference frequency in Hz (default 440).\n * @param refStep Step index of the reference (default 69).\n * @returns The tuning.\n * @example\n * ```ts\n * import { edo, frequencyOf } from '@libraz/libcantus';\n * const et19 = edo(19); // { refStep: 69, refFreq: 440, divisions: 19 }\n * frequencyOf(70, et19); // one 19-EDO step above A4\n * ```\n * @category Pitch & Intervals\n */\nexport function edo(n: number, refFreq = 440, refStep = 69): Tuning {\n assertPositiveInt(n, 'EDO divisions');\n assertRange(refFreq, Number.MIN_VALUE, Number.MAX_VALUE, 'reference frequency');\n assertFiniteNumber(refStep, 'reference step');\n return { refStep, refFreq, divisions: n };\n}\n\n/**\n * Frequency in Hz of a step index under a tuning.\n *\n * @param step Step index (a MIDI number under 12-EDO).\n * @param tuning The tuning (default 12-TET).\n * @returns The frequency in Hz.\n * @example\n * ```ts\n * import { frequencyOf } from '@libraz/libcantus';\n * frequencyOf(69); // 440 (A4 in 12-TET)\n * frequencyOf(60); // middle C in Hz\n * ```\n * @category Pitch & Intervals\n */\nexport function frequencyOf(step: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(step, 'step');\n assertTuning(tuning);\n const result = tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);\n return assertRange(result, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency result');\n}\n\n/**\n * Nearest step index to a frequency under a tuning (the inverse of\n * {@link frequencyOf}, rounded).\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The nearest step index.\n * @category Pitch & Intervals\n */\nexport function nearestStep(freq: number, tuning: Tuning = TWELVE_TET): number {\n assertRange(freq, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency');\n assertTuning(tuning);\n return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));\n}\n\n/**\n * Step index of a frequency under a tuning, unrounded.\n *\n * The exact inverse of {@link frequencyOf}. {@link nearestStep} rounds it,\n * which discards how far off the pitch actually is — the figure a tuner\n * display, a pitch-bend amount, or an intonation check is built on.\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The fractional step index.\n * @example\n * ```ts\n * import { stepOf } from '@libraz/libcantus';\n * stepOf(442); // slightly above 69, the step for A440\n * ```\n * @category Pitch & Intervals\n */\nexport function stepOf(freq: number, tuning: Tuning = TWELVE_TET): number {\n assertRange(freq, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency');\n assertTuning(tuning);\n return tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq);\n}\n\n/**\n * How far a frequency sits from its nearest step, in cents.\n *\n * Positive means sharp of the step, negative flat.\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The signed deviation in cents.\n * @example\n * ```ts\n * import { centsFromNearestStep } from '@libraz/libcantus';\n * Math.round(centsFromNearestStep(442)); // 8\n * ```\n * @category Pitch & Intervals\n */\nexport function centsFromNearestStep(freq: number, tuning: Tuning = TWELVE_TET): number {\n const exact = stepOf(freq, tuning);\n return centsOfSteps(exact - Math.round(exact), tuning);\n}\n\n/**\n * Steps spanned by an interval in cents, unrounded — the inverse of\n * {@link centsOfSteps}.\n *\n * @param cents The interval in cents.\n * @param tuning The tuning (default 12-TET).\n * @returns The fractional step count.\n * @category Pitch & Intervals\n */\nexport function stepsOfCents(cents: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(cents, 'cents');\n assertTuning(tuning);\n return assertFiniteNumber((cents * tuning.divisions) / 1200, 'steps result');\n}\n\n/**\n * The frequency ratio an interval in cents spans — the inverse of\n * {@link ratioToCents}.\n *\n * Multiply a frequency by this to move it by that many cents, which is how a\n * cents offset (a pitch-bend spec, a tuning table entry) is applied.\n *\n * @param cents The interval in cents.\n * @returns The frequency ratio.\n * @example\n * ```ts\n * import { centsToRatio } from '@libraz/libcantus';\n * 440 * centsToRatio(14); // A440 raised by 14 cents\n * ```\n * @category Pitch & Intervals\n */\nexport function centsToRatio(cents: number): number {\n assertFiniteNumber(cents, 'cents');\n return 2 ** (cents / 1200);\n}\n\n/**\n * Interval in cents between two frequencies.\n *\n * @param a Lower/first frequency in Hz.\n * @param b Upper/second frequency in Hz.\n * @returns Cents from `a` to `b` (negative if `b` is lower).\n * @category Pitch & Intervals\n */\nexport function centsBetweenFreq(a: number, b: number): number {\n assertRange(a, Number.MIN_VALUE, Number.MAX_VALUE, 'first frequency');\n assertRange(b, Number.MIN_VALUE, Number.MAX_VALUE, 'second frequency');\n return 1200 * (Math.log2(b) - Math.log2(a));\n}\n\n/**\n * Cents spanned by a number of equal-temperament steps.\n *\n * @param steps Number of steps.\n * @param tuning The tuning (default 12-TET).\n * @returns The cents.\n * @category Pitch & Intervals\n */\nexport function centsOfSteps(steps: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(steps, 'steps');\n assertTuning(tuning);\n return assertFiniteNumber((steps * 1200) / tuning.divisions, 'cents result');\n}\n\n/**\n * Cents of a frequency ratio, e.g. `ratioToCents(3, 2)` ≈ 701.955 for the just\n * perfect fifth.\n *\n * @param numerator Ratio numerator.\n * @param denominator Ratio denominator.\n * @returns The interval in cents.\n * @category Pitch & Intervals\n */\nexport function ratioToCents(numerator: number, denominator: number): number {\n assertRange(numerator, Number.MIN_VALUE, Number.MAX_VALUE, 'ratio numerator');\n assertRange(denominator, Number.MIN_VALUE, Number.MAX_VALUE, 'ratio denominator');\n return 1200 * (Math.log2(numerator) - Math.log2(denominator));\n}\n\n/**\n * Five-limit just-intonation ratios for the twelve interval classes above a\n * unison, indexed by semitone class (0..12).\n *\n * @category Pitch & Intervals\n */\nfunction justRatio(numerator: number, denominator: number): readonly [number, number] {\n return Object.freeze([numerator, denominator] as [number, number]);\n}\n\n/**\n * Five-limit just-intonation ratios for the twelve interval classes above a\n * unison, indexed by semitone class (0..12).\n *\n * @category Pitch & Intervals\n */\nexport const JUST_RATIOS: Readonly<Record<number, readonly [number, number]>> = Object.freeze({\n 0: justRatio(1, 1),\n 1: justRatio(16, 15),\n 2: justRatio(9, 8),\n 3: justRatio(6, 5),\n 4: justRatio(5, 4),\n 5: justRatio(4, 3),\n 6: justRatio(45, 32),\n 7: justRatio(3, 2),\n 8: justRatio(8, 5),\n 9: justRatio(5, 3),\n 10: justRatio(9, 5),\n 11: justRatio(15, 8),\n 12: justRatio(2, 1),\n});\n\n/**\n * Cents by which a five-limit just interval departs from its 12-TET tempering\n * (positive means the just interval is wider).\n *\n * @param semitoneClass Semitone class in [0, 12].\n * @returns The deviation in cents.\n * @throws If `semitoneClass` is not an integer in [0, 12].\n * @category Pitch & Intervals\n */\nexport function justDeviationCents(semitoneClass: number): number {\n assertInteger(semitoneClass, 'semitone class', 0, 12);\n const ratio = JUST_RATIOS[semitoneClass];\n if (!ratio) {\n throw new InvalidInputError(`semitone class has no just ratio: ${semitoneClass}`);\n }\n return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;\n}\n"]}
|