@libraz/libcantus 0.9.4 → 1.0.0
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 +61 -261
- package/README_ja.md +115 -0
- package/dist/analyze/index.cjs +54 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +213 -456
- package/dist/analyze/index.d.ts +213 -456
- package/dist/analyze/index.js +56 -9
- package/dist/chunk-3JXCW6DX.cjs +3220 -0
- package/dist/chunk-3JXCW6DX.cjs.map +1 -0
- package/dist/chunk-4A2RMUCC.js +7647 -0
- package/dist/chunk-4A2RMUCC.js.map +1 -0
- package/dist/chunk-6XPTBE7Y.cjs +49 -0
- package/dist/chunk-6XPTBE7Y.cjs.map +1 -0
- package/dist/chunk-AFM2ULJH.cjs +5121 -0
- package/dist/chunk-AFM2ULJH.cjs.map +1 -0
- package/dist/chunk-E3F4AHAH.cjs +690 -0
- package/dist/chunk-E3F4AHAH.cjs.map +1 -0
- package/dist/chunk-ELCTZUJJ.js +379 -0
- package/dist/chunk-ELCTZUJJ.js.map +1 -0
- package/dist/chunk-FVSI36UV.js +690 -0
- package/dist/chunk-FVSI36UV.js.map +1 -0
- package/dist/chunk-GEDTJHAC.cjs +1 -0
- package/dist/chunk-GEDTJHAC.cjs.map +1 -0
- package/dist/chunk-GOZG57ER.js +3220 -0
- package/dist/chunk-GOZG57ER.js.map +1 -0
- package/dist/chunk-H3VORETI.js +2243 -0
- package/dist/chunk-H3VORETI.js.map +1 -0
- package/dist/chunk-H42UHPYW.js +1577 -0
- package/dist/chunk-H42UHPYW.js.map +1 -0
- package/dist/chunk-HFPS7JFD.js +1 -0
- package/dist/chunk-INHVXP6Z.js +3053 -0
- package/dist/chunk-INHVXP6Z.js.map +1 -0
- package/dist/chunk-K4ZJUGF4.cjs +379 -0
- package/dist/chunk-K4ZJUGF4.cjs.map +1 -0
- package/dist/chunk-KNJEKLKV.js +49 -0
- package/dist/chunk-KNJEKLKV.js.map +1 -0
- package/dist/chunk-M4CWTJWJ.cjs +7647 -0
- package/dist/chunk-M4CWTJWJ.cjs.map +1 -0
- package/dist/chunk-MXO72PLY.cjs +211 -0
- package/dist/chunk-MXO72PLY.cjs.map +1 -0
- package/dist/chunk-NLEU4QSE.cjs +360 -0
- package/dist/chunk-NLEU4QSE.cjs.map +1 -0
- package/dist/chunk-OE6WK2Y2.cjs +1577 -0
- package/dist/chunk-OE6WK2Y2.cjs.map +1 -0
- package/dist/chunk-Q2WGKJNN.cjs +1064 -0
- package/dist/chunk-Q2WGKJNN.cjs.map +1 -0
- package/dist/chunk-QYQQ5J6E.js +1 -0
- package/dist/chunk-QYQQ5J6E.js.map +1 -0
- package/dist/chunk-RHIJ3S4I.cjs +1 -0
- package/dist/chunk-RHIJ3S4I.cjs.map +1 -0
- package/dist/chunk-TTCLJX6D.js +211 -0
- package/dist/chunk-TTCLJX6D.js.map +1 -0
- package/dist/chunk-UADYAYAT.cjs +3053 -0
- package/dist/chunk-UADYAYAT.cjs.map +1 -0
- package/dist/chunk-VOHZB45S.js +5121 -0
- package/dist/chunk-VOHZB45S.js.map +1 -0
- package/dist/chunk-VTI6FOLB.js +1064 -0
- package/dist/chunk-VTI6FOLB.js.map +1 -0
- package/dist/chunk-WWURE4XT.cjs +2243 -0
- package/dist/chunk-WWURE4XT.cjs.map +1 -0
- package/dist/chunk-YH3BIKHH.js +360 -0
- package/dist/chunk-YH3BIKHH.js.map +1 -0
- package/dist/core/index.cjs +103 -6
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +96 -345
- package/dist/core/index.d.ts +96 -345
- package/dist/core/index.js +104 -7
- package/dist/generate/index.cjs +89 -10
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +437 -1095
- package/dist/generate/index.d.ts +437 -1095
- package/dist/generate/index.js +94 -15
- package/dist/index-9wcMhG73.d.ts +1095 -0
- package/dist/{index-D6b6na69.d.cts → index-B4o55NDr.d.cts} +13 -9
- package/dist/index-B9MNZboQ.d.cts +770 -0
- package/dist/index-B9MNZboQ.d.ts +770 -0
- package/dist/index-BGFW-8P2.d.cts +2307 -0
- package/dist/index-BO6xE9YU.d.cts +1804 -0
- package/dist/index-BkGmlyI3.d.cts +470 -0
- package/dist/index-CCNJRG-P.d.cts +1095 -0
- package/dist/index-CGpRv5sl.d.cts +643 -0
- package/dist/index-C_8o--VE.d.cts +1242 -0
- package/dist/index-CgHqxdps.d.ts +2307 -0
- package/dist/index-Chbst3Jx.d.ts +1343 -0
- package/dist/index-Cj49F892.d.ts +470 -0
- package/dist/{index-tSQh-xwY.d.ts → index-CunYKLiu.d.ts} +13 -9
- package/dist/index-D57QRbVn.d.ts +1804 -0
- package/dist/index-DInQqT8Q.d.cts +66 -0
- package/dist/index-DInQqT8Q.d.ts +66 -0
- package/dist/index-Dzr13q3d.d.cts +1343 -0
- package/dist/index-G1uwGGWQ.d.ts +643 -0
- package/dist/index-szBv3496.d.ts +1242 -0
- package/dist/index.cjs +358 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -15
- package/dist/index.d.ts +17 -15
- package/dist/index.js +407 -64
- package/dist/masks-DYbE485J.d.cts +445 -0
- package/dist/masks-DYbE485J.d.ts +445 -0
- package/dist/model/index.cjs +39 -7
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +4685 -243
- package/dist/model/index.d.ts +4685 -243
- package/dist/model/index.js +40 -8
- package/dist/positional-Cptlj-xs.d.ts +287 -0
- package/dist/positional-DvH_sd8u.d.cts +287 -0
- package/dist/theory/index.cjs +101 -7
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +513 -173
- package/dist/theory/index.d.ts +513 -173
- package/dist/theory/index.js +118 -24
- package/package.json +16 -11
- package/dist/chunk-36N73KZ3.cjs +0 -246
- package/dist/chunk-36N73KZ3.cjs.map +0 -1
- package/dist/chunk-4HLLZPXT.cjs +0 -3077
- package/dist/chunk-4HLLZPXT.cjs.map +0 -1
- package/dist/chunk-7MGZ7D62.cjs +0 -498
- package/dist/chunk-7MGZ7D62.cjs.map +0 -1
- package/dist/chunk-7NKDPF74.js +0 -3077
- package/dist/chunk-7NKDPF74.js.map +0 -1
- package/dist/chunk-CAN76NCU.cjs +0 -679
- package/dist/chunk-CAN76NCU.cjs.map +0 -1
- package/dist/chunk-D2FAJBYL.js +0 -479
- package/dist/chunk-D2FAJBYL.js.map +0 -1
- package/dist/chunk-EHVEBWM5.js +0 -1449
- package/dist/chunk-EHVEBWM5.js.map +0 -1
- package/dist/chunk-FCANXBAT.cjs +0 -30
- package/dist/chunk-FCANXBAT.cjs.map +0 -1
- package/dist/chunk-FNDWXRRI.cjs +0 -479
- package/dist/chunk-FNDWXRRI.cjs.map +0 -1
- package/dist/chunk-GQSGS2N4.js +0 -173
- package/dist/chunk-GQSGS2N4.js.map +0 -1
- package/dist/chunk-HKGYCQ34.js +0 -53
- package/dist/chunk-HKGYCQ34.js.map +0 -1
- package/dist/chunk-I24MV65U.js +0 -106
- package/dist/chunk-I24MV65U.js.map +0 -1
- package/dist/chunk-KMHOICIY.js +0 -679
- package/dist/chunk-KMHOICIY.js.map +0 -1
- package/dist/chunk-LW53AHQU.cjs +0 -1117
- package/dist/chunk-LW53AHQU.cjs.map +0 -1
- package/dist/chunk-N6IHQUOX.js +0 -1
- package/dist/chunk-NKIBQ6PT.js +0 -30
- package/dist/chunk-NKIBQ6PT.js.map +0 -1
- package/dist/chunk-NP4NA2FB.cjs +0 -106
- package/dist/chunk-NP4NA2FB.cjs.map +0 -1
- package/dist/chunk-NVE34YGZ.cjs +0 -1449
- package/dist/chunk-NVE34YGZ.cjs.map +0 -1
- package/dist/chunk-NYYWMV2C.js +0 -327
- package/dist/chunk-NYYWMV2C.js.map +0 -1
- package/dist/chunk-Q2XUP227.js +0 -845
- package/dist/chunk-Q2XUP227.js.map +0 -1
- package/dist/chunk-Q6OP4U43.cjs +0 -327
- package/dist/chunk-Q6OP4U43.cjs.map +0 -1
- package/dist/chunk-RC6GBMCD.cjs +0 -845
- package/dist/chunk-RC6GBMCD.cjs.map +0 -1
- package/dist/chunk-RK35RIN7.js +0 -1117
- package/dist/chunk-RK35RIN7.js.map +0 -1
- package/dist/chunk-RUMU3UA6.cjs +0 -53
- package/dist/chunk-RUMU3UA6.cjs.map +0 -1
- package/dist/chunk-T64C7ZD7.cjs +0 -1
- package/dist/chunk-T64C7ZD7.cjs.map +0 -1
- package/dist/chunk-XEBG3G53.js +0 -498
- package/dist/chunk-XEBG3G53.js.map +0 -1
- package/dist/chunk-YLEMGEFH.js +0 -246
- package/dist/chunk-YLEMGEFH.js.map +0 -1
- package/dist/chunk-ZEJR4TKL.cjs +0 -173
- package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
- package/dist/function-BtiZb9i9.d.ts +0 -208
- package/dist/function-DNugAOrt.d.cts +0 -208
- package/dist/index-6VR8-kit.d.ts +0 -143
- package/dist/index-70p168VM.d.ts +0 -192
- package/dist/index-Be74mcon.d.cts +0 -152
- package/dist/index-BtXT6UQ0.d.cts +0 -192
- package/dist/index-C5UBbQ4G.d.ts +0 -152
- package/dist/index-C83mZI1P.d.ts +0 -242
- package/dist/index-DXd9JikN.d.cts +0 -246
- package/dist/index-DXd9JikN.d.ts +0 -246
- package/dist/index-OG65ocOr.d.cts +0 -272
- package/dist/index-OG65ocOr.d.ts +0 -272
- package/dist/index-tJhkmQgE.d.cts +0 -143
- package/dist/index-y_ak9_Hi.d.cts +0 -242
- package/dist/styled-DIRkk9Uz.d.ts +0 -506
- package/dist/styled-DJbkJQfj.d.cts +0 -506
- package/dist/types-BN6_Q9Kl.d.cts +0 -39
- package/dist/types-BN6_Q9Kl.d.ts +0 -39
- /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
var _chunkUADYAYATcjs = require('./chunk-UADYAYAT.cjs');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
var _chunkQ2WGKJNNcjs = require('./chunk-Q2WGKJNN.cjs');
|
|
17
|
+
|
|
18
|
+
// src/analyze/spelling/index.ts
|
|
19
|
+
var CHORD_MISMATCH = 64;
|
|
20
|
+
var SCALE_TONE_MISMATCH = 32;
|
|
21
|
+
var LETTER_STEP_DEVIATION = 4;
|
|
22
|
+
var MAX_COUNTED_DEVIATION = 2;
|
|
23
|
+
var AGAINST_DIRECTION = 3;
|
|
24
|
+
var KEY_MISMATCH = 4;
|
|
25
|
+
var ACCIDENTAL = 1;
|
|
26
|
+
var MAX_STEP = 2;
|
|
27
|
+
var MAX_ALTER = 2;
|
|
28
|
+
var SPELLINGS_PER_PITCH = 3;
|
|
29
|
+
var CONVENTIONAL_LETTER_STEPS = [
|
|
30
|
+
[0],
|
|
31
|
+
// unison
|
|
32
|
+
[0, 1],
|
|
33
|
+
// chromatic semitone or diatonic semitone
|
|
34
|
+
[1],
|
|
35
|
+
// second
|
|
36
|
+
[2],
|
|
37
|
+
// minor third
|
|
38
|
+
[2],
|
|
39
|
+
// major third
|
|
40
|
+
[3],
|
|
41
|
+
// fourth
|
|
42
|
+
[3, 4],
|
|
43
|
+
// augmented fourth or diminished fifth
|
|
44
|
+
[4],
|
|
45
|
+
// fifth
|
|
46
|
+
[5],
|
|
47
|
+
// minor sixth
|
|
48
|
+
[5],
|
|
49
|
+
// major sixth
|
|
50
|
+
[6],
|
|
51
|
+
// minor seventh
|
|
52
|
+
[6]
|
|
53
|
+
// major seventh
|
|
54
|
+
];
|
|
55
|
+
function alterationFor(letter, pc) {
|
|
56
|
+
const distance = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc - _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, letter));
|
|
57
|
+
return distance > 6 ? distance - 12 : distance;
|
|
58
|
+
}
|
|
59
|
+
function octaveFor(pitch, letter, alter) {
|
|
60
|
+
return (pitch - _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, letter) - alter) / 12 - 1;
|
|
61
|
+
}
|
|
62
|
+
function sameSpelling(a, b) {
|
|
63
|
+
return a.letter === b.letter && a.alter === b.alter;
|
|
64
|
+
}
|
|
65
|
+
function candidatesFor(pitch, preferred) {
|
|
66
|
+
const pc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pitch);
|
|
67
|
+
const rest = [];
|
|
68
|
+
for (let letter = 0; letter < 7; letter += 1) {
|
|
69
|
+
const alter = alterationFor(letter, pc);
|
|
70
|
+
if (Math.abs(alter) > MAX_ALTER || letter === preferred.letter) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
rest.push({ letter, alter, octave: octaveFor(pitch, letter, alter) });
|
|
74
|
+
}
|
|
75
|
+
rest.sort((a, b) => Math.abs(a.alter) - Math.abs(b.alter) || a.letter - b.letter);
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
letter: preferred.letter,
|
|
79
|
+
alter: preferred.alter,
|
|
80
|
+
octave: octaveFor(pitch, preferred.letter, preferred.alter)
|
|
81
|
+
},
|
|
82
|
+
...rest
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
function chordSpellingOf(pc, chord, tonic, key) {
|
|
86
|
+
for (const tone of _chunkUADYAYATcjs.spellChord.call(void 0, chord, tonic, key)) {
|
|
87
|
+
if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, tone.letter) + tone.alter) === pc) {
|
|
88
|
+
return tone;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const bass = chord.bassPc;
|
|
92
|
+
if (bass === void 0 || _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, bass) !== pc) {
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
const hint = chord.bassSpelling;
|
|
96
|
+
return hint !== void 0 && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, hint.letter) + hint.alter) === pc ? { letter: _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, hint.letter), alter: hint.alter } : _chunkUADYAYATcjs.spellPitchClass.call(void 0, pc, tonic, key);
|
|
97
|
+
}
|
|
98
|
+
function stateFor(pitch, chord, tonic, key) {
|
|
99
|
+
const pc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pitch);
|
|
100
|
+
const fromChord = chord === null ? void 0 : chordSpellingOf(pc, chord, tonic, key);
|
|
101
|
+
const scaleTone = _chunkUADYAYATcjs.isScaleTone.call(void 0, pc, key);
|
|
102
|
+
const reference = _nullishCoalesce(fromChord, () => ( _chunkUADYAYATcjs.spellPitchClass.call(void 0, pc, tonic, key)));
|
|
103
|
+
const mismatch = fromChord !== void 0 ? CHORD_MISMATCH : scaleTone ? SCALE_TONE_MISMATCH : KEY_MISMATCH;
|
|
104
|
+
const candidates = candidatesFor(pitch, reference);
|
|
105
|
+
return {
|
|
106
|
+
pitch,
|
|
107
|
+
chromatic: !scaleTone,
|
|
108
|
+
candidates,
|
|
109
|
+
emissions: candidates.map(
|
|
110
|
+
(candidate) => (sameSpelling(candidate, reference) ? 0 : mismatch) + ACCIDENTAL * Math.abs(candidate.alter)
|
|
111
|
+
),
|
|
112
|
+
reference
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function diatonicIndex(note) {
|
|
116
|
+
return note.letter + 7 * (_nullishCoalesce(note.octave, () => ( 0)));
|
|
117
|
+
}
|
|
118
|
+
function letterStepCost(from, to, semitones) {
|
|
119
|
+
const written = diatonicIndex(to) - diatonicIndex(from);
|
|
120
|
+
const size = Math.abs(semitones);
|
|
121
|
+
const octaves = Math.floor(size / 12);
|
|
122
|
+
const direction = semitones < 0 ? -1 : 1;
|
|
123
|
+
let deviation = Number.POSITIVE_INFINITY;
|
|
124
|
+
for (const steps of _nullishCoalesce(CONVENTIONAL_LETTER_STEPS[size % 12], () => ( []))) {
|
|
125
|
+
deviation = Math.min(deviation, Math.abs(written - direction * (octaves * 7 + steps)));
|
|
126
|
+
}
|
|
127
|
+
if (!Number.isFinite(deviation)) {
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
return LETTER_STEP_DEVIATION * Math.min(deviation, MAX_COUNTED_DEVIATION);
|
|
131
|
+
}
|
|
132
|
+
function directionCost(note, chromatic, semitones) {
|
|
133
|
+
if (!chromatic || semitones === 0 || Math.abs(semitones) > MAX_STEP) {
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
const rising = semitones > 0;
|
|
137
|
+
return rising && note.alter < 0 || !rising && note.alter > 0 ? AGAINST_DIRECTION : 0;
|
|
138
|
+
}
|
|
139
|
+
function transitionCost(from, source, to, target) {
|
|
140
|
+
const semitones = to.pitch - from.pitch;
|
|
141
|
+
return letterStepCost(source, target, semitones) + directionCost(target, to.chromatic, semitones) + directionCost(source, from.chromatic, semitones);
|
|
142
|
+
}
|
|
143
|
+
function spellLine(notes, timeline, key, opts = {}) {
|
|
144
|
+
const scale = _chunkUADYAYATcjs.toKeyScale.call(void 0, key);
|
|
145
|
+
const given = opts.tonic === void 0 ? void 0 : _chunkQ2WGKJNNcjs.toNoteData.call(void 0, opts.tonic);
|
|
146
|
+
_chunkQ2WGKJNNcjs.assertNoteEvents.call(void 0, notes, "line notes", {
|
|
147
|
+
allowNonPositiveDuration: true,
|
|
148
|
+
budget: opts.budget
|
|
149
|
+
});
|
|
150
|
+
_chunkQ2WGKJNNcjs.assertGenerationBudget.call(void 0,
|
|
151
|
+
notes.length * SPELLINGS_PER_PITCH * SPELLINGS_PER_PITCH,
|
|
152
|
+
"line spelling states",
|
|
153
|
+
opts.budget
|
|
154
|
+
);
|
|
155
|
+
if (given !== void 0) {
|
|
156
|
+
_chunkUADYAYATcjs.assertTonicOf.call(void 0, given, scale, "spellLine");
|
|
157
|
+
}
|
|
158
|
+
if (notes.length === 0) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const tonic = _nullishCoalesce(given, () => ( _chunkUADYAYATcjs.spelledKeyOf.call(void 0, scale).tonic));
|
|
162
|
+
const states = notes.map(
|
|
163
|
+
(note) => stateFor(note.pitch, _nullishCoalesce(_optionalChain([timeline, 'optionalAccess', _ => _.at, 'call', _2 => _2(note.startBeat)]), () => ( null)), tonic, scale)
|
|
164
|
+
);
|
|
165
|
+
let costs = [];
|
|
166
|
+
let previous;
|
|
167
|
+
const back = [];
|
|
168
|
+
for (const current of states) {
|
|
169
|
+
if (previous === void 0) {
|
|
170
|
+
costs = [...current.emissions];
|
|
171
|
+
back.push(current.candidates.map(() => 0));
|
|
172
|
+
previous = current;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const reached = [];
|
|
176
|
+
const chosen = [];
|
|
177
|
+
for (const [target, candidate] of current.candidates.entries()) {
|
|
178
|
+
let best = Number.POSITIVE_INFINITY;
|
|
179
|
+
let bestSource = 0;
|
|
180
|
+
for (const [source, sourceNote] of previous.candidates.entries()) {
|
|
181
|
+
const total = (_nullishCoalesce(costs[source], () => ( Number.POSITIVE_INFINITY))) + transitionCost(previous, sourceNote, current, candidate);
|
|
182
|
+
if (total < best) {
|
|
183
|
+
best = total;
|
|
184
|
+
bestSource = source;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
reached.push(best + (_nullishCoalesce(current.emissions[target], () => ( 0))));
|
|
188
|
+
chosen.push(bestSource);
|
|
189
|
+
}
|
|
190
|
+
costs = reached;
|
|
191
|
+
back.push(chosen);
|
|
192
|
+
previous = current;
|
|
193
|
+
}
|
|
194
|
+
let cursor = 0;
|
|
195
|
+
for (const [index, cost] of costs.entries()) {
|
|
196
|
+
if (cost < (_nullishCoalesce(costs[cursor], () => ( Number.POSITIVE_INFINITY)))) {
|
|
197
|
+
cursor = index;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const picked = new Array(states.length).fill(0);
|
|
201
|
+
for (let index = states.length - 1; index >= 0; index -= 1) {
|
|
202
|
+
picked[index] = cursor;
|
|
203
|
+
cursor = _nullishCoalesce(_optionalChain([back, 'access', _3 => _3[index], 'optionalAccess', _4 => _4[cursor]]), () => ( 0));
|
|
204
|
+
}
|
|
205
|
+
return states.map((state, index) => _nullishCoalesce(state.candidates[_nullishCoalesce(picked[index], () => ( 0))], () => ( state.reference)));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
exports.spellLine = spellLine;
|
|
211
|
+
//# sourceMappingURL=chunk-MXO72PLY.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-MXO72PLY.cjs","../src/analyze/spelling/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-MXO72PLY.cjs","sourcesContent":[null,"/**\n * Line spelling: name every note of a melodic line in one pass.\n *\n * {@link spellPitch} decides one note from the key, the root of the chord under\n * it and its two immediate neighbours. That is enough for a single note, but a\n * line spelled note by note can zig-zag between the two readings of one idea —\n * a rising whole-tone figure comes out F# Ab Bb, with a diminished third in the\n * middle of it.\n *\n * This module solves the whole line at once. The theory layer's own judgment\n * becomes the cost of emitting a spelling, the melodic rules become the cost of\n * moving from one spelling to the next, and a dynamic program takes the\n * cheapest path across the line. Nothing here re-decides what\n * {@link spellPitchClass} and {@link spellChord} already decide: it only weighs\n * their answers against the shape of the line they sit in.\n */\n\nimport type { Note, NoteLike } from '../../core/pitch/index.js';\nimport {\n diatonicLetterOf as mod7,\n pitchClassOf as mod12,\n naturalPitchClassOf as naturalPc,\n toNoteData,\n} from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport { assertGenerationBudget, assertNoteEvents } from '../../core/validation/index.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { isScaleTone, type KeyLike, spelledKeyOf, toKeyScale } from '../../theory/scale/index.js';\nimport { assertTonicOf, spellChord, spellPitchClass } from '../../theory/spelling/index.js';\nimport type { ChordTimeline } from '../timeline/index.js';\n\n/**\n * The four rules, and what happens where they pull against each other.\n *\n * The costs below are spread far enough apart that a rule can never be\n * outvoted by any number of the rules under it, which fixes the precedence:\n *\n * 1. **The sounding chord.** A note the chord names takes the chord's own\n * spelling of that tone. This is the one place the line departs from\n * {@link spellPitchClass}, which never respells a tone of the key: over a\n * Db7 the seventh has to read Cb even in C major, where B is a scale tone,\n * because a dominant seventh spelled as a sixth is not the chord that was\n * played.\n * 2. **The key's scale.** Anything else the key itself spells — a scale tone,\n * and the tonic — keeps that spelling, exactly as the note-by-note path\n * keeps it. No amount of melodic pressure moves it.\n * 3. **The shape of the line, then its direction.** A chromatic note is spelled\n * so the letters of the intervals around it match the intervals actually\n * sounding, and a stepwise line then prefers sharps rising and flats\n * falling. Shape outranks direction because a rising line spelled with\n * sharps is still wrong if it spells a whole tone as a diminished third.\n * 4. **The key's chromatic convention, then the fewest accidentals.** What the\n * key alone would have said, as the tiebreak it is: the direction rule beats\n * it, which is why a chromatic ascent in C major spells C# D# G# A# rather\n * than the flats the key prefers, but one-sided evidence — a chromatic note\n * with only one stepwise neighbour — does not.\n */\n\n/** Emission cost for a spelling the chord sounding under the note does not name. */\nconst CHORD_MISMATCH = 64;\n\n/** Emission cost for respelling a tone the key's own scale already names. */\nconst SCALE_TONE_MISMATCH = 32;\n\n/** Transition cost per diatonic step an interval's letters miss its written size by. */\nconst LETTER_STEP_DEVIATION = 4;\n\n/**\n * How many steps of deviation are counted before the cost stops growing.\n *\n * Two letters out is already an interval no reading supports, and capping there\n * keeps the whole melodic term below {@link SCALE_TONE_MISMATCH}, so a badly\n * shaped line can never argue a scale tone out of its key spelling.\n */\nconst MAX_COUNTED_DEVIATION = 2;\n\n/** Transition cost for an accidental leaning against the direction of a stepwise line. */\nconst AGAINST_DIRECTION = 3;\n\n/** Emission cost for a chromatic spelling the key alone would not choose. */\nconst KEY_MISMATCH = 4;\n\n/** Emission cost per semitone of alteration: what minimises accidentals overall. */\nconst ACCIDENTAL = 1;\n\n/** Widest melodic interval still counted as a step, where the direction rule applies. */\nconst MAX_STEP = 2;\n\n/** Widest alteration a spelling may carry: a double accidental. */\nconst MAX_ALTER = 2;\n\n/**\n * Spellings each pitch class has within a double accidental.\n *\n * Always exactly three — a natural or its accidental, one letter above, one\n * letter below (C is C, B# and Dbb) — which is what bounds the work the dynamic\n * program is allowed to do.\n */\nconst SPELLINGS_PER_PITCH = 3;\n\n/**\n * The diatonic letter distances a melodic interval of a given size is\n * conventionally written with, indexed by its semitone class.\n *\n * Most sizes read only one way: a whole tone is a second, a minor third is a\n * third, so spelling one as anything else costs. Two sizes read two ways and\n * both are free — a semitone is either a chromatic inflection of one letter\n * (F to F#) or a diatonic step (E to F), and the tritone is either an augmented\n * fourth or a diminished fifth. Direction chooses between those.\n */\nconst CONVENTIONAL_LETTER_STEPS: readonly (readonly number[])[] = [\n [0], // unison\n [0, 1], // chromatic semitone or diatonic semitone\n [1], // second\n [2], // minor third\n [2], // major third\n [3], // fourth\n [3, 4], // augmented fourth or diminished fifth\n [4], // fifth\n [5], // minor sixth\n [5], // major sixth\n [6], // minor seventh\n [6], // major seventh\n];\n\n/**\n * Options for {@link spellLine}.\n *\n * @category Arrangement & Analysis\n */\nexport type SpellLineOptions = {\n /**\n * Tonic spelling to anchor the key, for a caller that already knows how the\n * piece is written. It must sound the key's own root pitch class; anything\n * else is rejected rather than spelled. Defaults to the conventional\n * spelling `spelledKeyOf` gives the key. Accepted as a note name, a MIDI\n * number, note data, or a `Note`.\n */\n tonic?: NoteLike;\n /** Upper bound on the work the line may cost; defaults to the generation budget. */\n budget?: number;\n};\n\n/** Shortest signed alteration (in [-6, 6]) taking a letter's natural pc to `pc`. */\nfunction alterationFor(letter: number, pc: number): number {\n const distance = mod12(pc - naturalPc(letter));\n return distance > 6 ? distance - 12 : distance;\n}\n\n/** The octave that reproduces `pitch` from a letter and its alteration. */\nfunction octaveFor(pitch: number, letter: number, alter: number): number {\n return (pitch - naturalPc(letter) - alter) / 12 - 1;\n}\n\n/** Whether two spellings name the same letter with the same alteration. */\nfunction sameSpelling(a: Note, b: Note): boolean {\n return a.letter === b.letter && a.alter === b.alter;\n}\n\n/**\n * Every spelling of `pitch` within a double accidental, `preferred` first.\n *\n * The order is what settles a tie: two readings that cost the same are equally\n * defensible, and the one the theory layer would have chosen alone is then the\n * one that stands.\n */\nfunction candidatesFor(pitch: number, preferred: Note): Note[] {\n const pc = mod12(pitch);\n const rest: Note[] = [];\n for (let letter = 0; letter < 7; letter += 1) {\n const alter = alterationFor(letter, pc);\n if (Math.abs(alter) > MAX_ALTER || letter === preferred.letter) {\n continue;\n }\n rest.push({ letter, alter, octave: octaveFor(pitch, letter, alter) });\n }\n rest.sort((a, b) => Math.abs(a.alter) - Math.abs(b.alter) || a.letter - b.letter);\n return [\n {\n letter: preferred.letter,\n alter: preferred.alter,\n octave: octaveFor(pitch, preferred.letter, preferred.alter),\n },\n ...rest,\n ];\n}\n\n/**\n * The chord's own spelling of a pitch class, or undefined when the chord does\n * not sound it.\n *\n * The tones come from {@link spellChord}, so a chord carrying its own spellings\n * — a parsed symbol, an augmented sixth — is read by them; a slash bass outside\n * the chord's tones is spelled from its hint when it has one.\n */\nfunction chordSpellingOf(pc: number, chord: Chord, tonic: Note, key: KeyScale): Note | undefined {\n for (const tone of spellChord(chord, tonic, key)) {\n if (mod12(naturalPc(tone.letter) + tone.alter) === pc) {\n return tone;\n }\n }\n const bass = chord.bassPc;\n if (bass === undefined || mod12(bass) !== pc) {\n return undefined;\n }\n const hint = chord.bassSpelling;\n return hint !== undefined && mod12(naturalPc(hint.letter) + hint.alter) === pc\n ? { letter: mod7(hint.letter), alter: hint.alter }\n : spellPitchClass(pc, tonic, key);\n}\n\n/** One note of the line, with the spellings open to it and what each costs. */\ntype LineState = {\n pitch: number;\n /** True when the key does not already spell this pitch, so direction may decide. */\n chromatic: boolean;\n candidates: Note[];\n emissions: number[];\n /** The spelling the note-by-note path would have chosen, used as the fallback. */\n reference: Note;\n};\n\n/**\n * Weigh every spelling open to one note against what the chord and the key say\n * about it.\n *\n * The reference spelling is the theory layer's own answer — the chord's, when\n * the chord names the note, and the key's otherwise — and the cost of departing\n * from it is what ranks the three rules that do not depend on the neighbours.\n */\nfunction stateFor(pitch: number, chord: Chord | null, tonic: Note, key: KeyScale): LineState {\n const pc = mod12(pitch);\n const fromChord = chord === null ? undefined : chordSpellingOf(pc, chord, tonic, key);\n const scaleTone = isScaleTone(pc, key);\n const reference = fromChord ?? spellPitchClass(pc, tonic, key);\n const mismatch =\n fromChord !== undefined ? CHORD_MISMATCH : scaleTone ? SCALE_TONE_MISMATCH : KEY_MISMATCH;\n const candidates = candidatesFor(pitch, reference);\n return {\n pitch,\n chromatic: !scaleTone,\n candidates,\n emissions: candidates.map(\n (candidate) =>\n (sameSpelling(candidate, reference) ? 0 : mismatch) +\n ACCIDENTAL * Math.abs(candidate.alter),\n ),\n reference,\n };\n}\n\n/** A spelling's position on the diatonic ladder, octave included. */\nfunction diatonicIndex(note: Note): number {\n return note.letter + 7 * (note.octave ?? 0);\n}\n\n/**\n * What the letters of a melodic interval cost when they do not match the\n * interval that actually sounds.\n *\n * Both notes are already spelled, so the sounding interval and the written one\n * are both known: a whole tone written F# to Ab is two letters apart where a\n * second is one, and pays for the difference.\n */\nfunction letterStepCost(from: Note, to: Note, semitones: number): number {\n const written = diatonicIndex(to) - diatonicIndex(from);\n const size = Math.abs(semitones);\n const octaves = Math.floor(size / 12);\n const direction = semitones < 0 ? -1 : 1;\n let deviation = Number.POSITIVE_INFINITY;\n for (const steps of CONVENTIONAL_LETTER_STEPS[size % 12] ?? []) {\n deviation = Math.min(deviation, Math.abs(written - direction * (octaves * 7 + steps)));\n }\n if (!Number.isFinite(deviation)) {\n return 0;\n }\n return LETTER_STEP_DEVIATION * Math.min(deviation, MAX_COUNTED_DEVIATION);\n}\n\n/**\n * What an accidental costs for leaning the wrong way in a stepwise line: a\n * rising line is written with sharps and a falling one with flats.\n *\n * Only steps are judged. The rule describes chromatic motion, and a leap onto a\n * flat says nothing about the flat — the key and the chord have the whole story\n * there.\n */\nfunction directionCost(note: Note, chromatic: boolean, semitones: number): number {\n if (!chromatic || semitones === 0 || Math.abs(semitones) > MAX_STEP) {\n return 0;\n }\n const rising = semitones > 0;\n return (rising && note.alter < 0) || (!rising && note.alter > 0) ? AGAINST_DIRECTION : 0;\n}\n\n/**\n * The cost of following one spelling with another.\n *\n * Direction is charged to both notes: a chromatic note is judged by the step it\n * arrives on and by the step it leaves on, so a note inside a run collects the\n * rule twice and a note at the end of one collects it once.\n */\nfunction transitionCost(from: LineState, source: Note, to: LineState, target: Note): number {\n const semitones = to.pitch - from.pitch;\n return (\n letterStepCost(source, target, semitones) +\n directionCost(target, to.chromatic, semitones) +\n directionCost(source, from.chromatic, semitones)\n );\n}\n\n/**\n * Spell a whole melodic line at once, so its notes agree with each other.\n *\n * Every note is spelled together with the rest of the line rather than one at a\n * time: each pitch offers the three spellings a double accidental allows, the\n * chord and the key price them, the intervals between neighbours price the\n * pairs, and the cheapest reading of the whole line wins. Ties fall to the\n * spelling {@link spellPitchClass} would have chosen alone, so the answer never\n * drifts from the theory layer without a reason.\n *\n * Four rules decide, in this order:\n *\n * 1. A note the sounding chord names takes that chord's spelling of it, even\n * when the key spells the pitch otherwise — the seventh of a Db7 reads Cb in\n * C major, not B.\n * 2. Anything else the key's scale spells keeps its key spelling, exactly as\n * the note-by-note path keeps it.\n * 3. A chromatic note is spelled so the written intervals around it match the\n * sounding ones, and then so a rising step carries a sharp and a falling\n * step a flat.\n * 4. Failing all of that, the key's own chromatic spelling stands, and the\n * fewest accidentals win.\n *\n * Without a timeline — pass `null` — rules 2 to 4 still apply in full, so a\n * diatonic line and a chromatic run come out the same. What is lost is rule 1:\n * a chromatic chord can no longer name its own tones, so the tones of a\n * borrowed or altered chord fall back to the key's convention (that Cb becomes\n * B), and an enharmonically ambiguous tone under a chord that would have fixed\n * it is decided by the line's direction instead.\n *\n * @param notes The line, monophonic and in time order. Notes sharing an onset\n * are read as consecutive, so split polyphonic material into voices first.\n * @param timeline The chords sounding under the line, or null when only the key\n * is known. Each note is judged against the chord at its own onset.\n * @param key The key the line is written in, as a key name, a key/scale, or a\n * `Key`.\n * @param opts Optional tonic spelling and work budget.\n * @returns One spelled note per input note, in input order, each carrying the\n * octave that reproduces its pitch.\n * @throws If `opts.tonic` does not sound the key's root pitch class, if a note\n * event is malformed, or if the line is longer than the budget allows.\n * @example\n * ```ts\n * import { chordTimelineFromChords, majorKey, noteNames, spellLine } from '@libraz/libcantus';\n * // A chromatic ascent spells with sharps, whatever the key would say alone.\n * const rising = [60, 61, 62, 63, 64].map((pitch, index) => ({\n * pitch,\n * startBeat: index,\n * durationBeat: 1,\n * }));\n * noteNames(spellLine(rising, null, majorKey(0)));\n * // ['C4', 'C#4', 'D4', 'D#4', 'E4']\n * // Under a Db7 the same B sounds as the chord's seventh.\n * const timeline = chordTimelineFromChords([{ rootPc: 1, quality: 'dom7', startBeat: 0 }], 4);\n * noteNames(spellLine([{ pitch: 71, startBeat: 0, durationBeat: 1 }], timeline, majorKey(0)));\n * // ['Cb5']\n * ```\n * @category Arrangement & Analysis\n */\nexport function spellLine(\n notes: readonly NoteEvent[],\n timeline: ChordTimeline | null,\n key: KeyLike,\n opts: SpellLineOptions = {},\n): Note[] {\n const scale = toKeyScale(key);\n const given = opts.tonic === undefined ? undefined : toNoteData(opts.tonic);\n assertNoteEvents(notes, 'line notes', {\n allowNonPositiveDuration: true,\n budget: opts.budget,\n });\n // The dynamic program compares every spelling of a note with every spelling\n // of the one before it, so the work is bounded before any of it is done.\n assertGenerationBudget(\n notes.length * SPELLINGS_PER_PITCH * SPELLINGS_PER_PITCH,\n 'line spelling states',\n opts.budget,\n );\n if (given !== undefined) {\n assertTonicOf(given, scale, 'spellLine');\n }\n if (notes.length === 0) {\n return [];\n }\n const tonic = given ?? spelledKeyOf(scale).tonic;\n const states = notes.map((note) =>\n stateFor(note.pitch, timeline?.at(note.startBeat) ?? null, tonic, scale),\n );\n\n // Viterbi across the line: `costs` holds the cheapest path reaching each\n // spelling of the current note, and `back` remembers which spelling of the\n // previous note that path came through.\n let costs: number[] = [];\n let previous: LineState | undefined;\n const back: number[][] = [];\n for (const current of states) {\n if (previous === undefined) {\n costs = [...current.emissions];\n back.push(current.candidates.map(() => 0));\n previous = current;\n continue;\n }\n const reached: number[] = [];\n const chosen: number[] = [];\n for (const [target, candidate] of current.candidates.entries()) {\n let best = Number.POSITIVE_INFINITY;\n let bestSource = 0;\n for (const [source, sourceNote] of previous.candidates.entries()) {\n const total =\n (costs[source] ?? Number.POSITIVE_INFINITY) +\n transitionCost(previous, sourceNote, current, candidate);\n if (total < best) {\n best = total;\n bestSource = source;\n }\n }\n reached.push(best + (current.emissions[target] ?? 0));\n chosen.push(bestSource);\n }\n costs = reached;\n back.push(chosen);\n previous = current;\n }\n\n let cursor = 0;\n for (const [index, cost] of costs.entries()) {\n if (cost < (costs[cursor] ?? Number.POSITIVE_INFINITY)) {\n cursor = index;\n }\n }\n const picked: number[] = new Array(states.length).fill(0);\n for (let index = states.length - 1; index >= 0; index -= 1) {\n picked[index] = cursor;\n cursor = back[index]?.[cursor] ?? 0;\n }\n return states.map((state, index) => state.candidates[picked[index] ?? 0] ?? state.reference);\n}\n"]}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
var _chunkQ2WGKJNNcjs = require('./chunk-Q2WGKJNN.cjs');
|
|
11
|
+
|
|
12
|
+
// src/core/duration/index.ts
|
|
13
|
+
var NOTE_VALUES = Object.freeze([
|
|
14
|
+
"whole",
|
|
15
|
+
"half",
|
|
16
|
+
"quarter",
|
|
17
|
+
"eighth",
|
|
18
|
+
"sixteenth",
|
|
19
|
+
"thirtySecond",
|
|
20
|
+
"sixtyFourth"
|
|
21
|
+
]);
|
|
22
|
+
var NOTE_VALUE_QUARTERS = Object.freeze({
|
|
23
|
+
whole: 4,
|
|
24
|
+
half: 2,
|
|
25
|
+
quarter: 1,
|
|
26
|
+
eighth: 0.5,
|
|
27
|
+
sixteenth: 0.25,
|
|
28
|
+
thirtySecond: 0.125,
|
|
29
|
+
sixtyFourth: 0.0625
|
|
30
|
+
});
|
|
31
|
+
var EPS = 1e-6;
|
|
32
|
+
var MAX_SPELLED_DOTS = 2;
|
|
33
|
+
var MAX_DOTS = 4;
|
|
34
|
+
var MAX_TUPLET = 64;
|
|
35
|
+
var SPELLED_TUPLETS = Object.freeze([
|
|
36
|
+
void 0,
|
|
37
|
+
{ actual: 3, normal: 2 },
|
|
38
|
+
{ actual: 5, normal: 4 },
|
|
39
|
+
{ actual: 7, normal: 4 }
|
|
40
|
+
]);
|
|
41
|
+
function quartersOfParts(base, dots, tuplet) {
|
|
42
|
+
const dotted = NOTE_VALUE_QUARTERS[base] * (2 - 2 ** -dots);
|
|
43
|
+
return tuplet === void 0 ? dotted : dotted * tuplet.normal / tuplet.actual;
|
|
44
|
+
}
|
|
45
|
+
function assertDuration(value, name) {
|
|
46
|
+
if (typeof value === "string") {
|
|
47
|
+
return { base: _chunkQ2WGKJNNcjs.assertOneOf.call(void 0, value, NOTE_VALUES, name), dots: 0 };
|
|
48
|
+
}
|
|
49
|
+
if (typeof value !== "object" || value === null) {
|
|
50
|
+
throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name} must be a note value or a duration; received ${value}`);
|
|
51
|
+
}
|
|
52
|
+
const base = _chunkQ2WGKJNNcjs.assertOneOf.call(void 0, value.base, NOTE_VALUES, `${name}.base`);
|
|
53
|
+
const dots = value.dots === void 0 ? 0 : _chunkQ2WGKJNNcjs.assertInteger.call(void 0, value.dots, `${name}.dots`, 0, MAX_DOTS);
|
|
54
|
+
if (value.tuplet === void 0) {
|
|
55
|
+
return { base, dots };
|
|
56
|
+
}
|
|
57
|
+
const tuplet = {
|
|
58
|
+
actual: _chunkQ2WGKJNNcjs.assertPositiveInt.call(void 0, value.tuplet.actual, `${name}.tuplet.actual`, MAX_TUPLET),
|
|
59
|
+
normal: _chunkQ2WGKJNNcjs.assertPositiveInt.call(void 0, value.tuplet.normal, `${name}.tuplet.normal`, MAX_TUPLET)
|
|
60
|
+
};
|
|
61
|
+
return { base, dots, tuplet };
|
|
62
|
+
}
|
|
63
|
+
function quartersOf(duration) {
|
|
64
|
+
return quartersOfParts(duration.base, _nullishCoalesce(duration.dots, () => ( 0)), duration.tuplet);
|
|
65
|
+
}
|
|
66
|
+
function beatQuarters(options) {
|
|
67
|
+
if (options.beatUnit === void 0) {
|
|
68
|
+
return NOTE_VALUE_QUARTERS.quarter;
|
|
69
|
+
}
|
|
70
|
+
return quartersOf(assertDuration(options.beatUnit, "beatUnit"));
|
|
71
|
+
}
|
|
72
|
+
function spellQuarters(quarters) {
|
|
73
|
+
for (const tuplet of SPELLED_TUPLETS) {
|
|
74
|
+
for (let dots = 0; dots <= MAX_SPELLED_DOTS; dots += 1) {
|
|
75
|
+
for (const base of NOTE_VALUES) {
|
|
76
|
+
if (Math.abs(quartersOfParts(base, dots, tuplet) - quarters) <= EPS) {
|
|
77
|
+
return tuplet === void 0 ? { base, dots } : { base, dots, tuplet: { actual: tuplet.actual, normal: tuplet.normal } };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
var PLAIN_VALUES = Object.freeze(
|
|
85
|
+
NOTE_VALUES.map((base) => ({ base, quarters: NOTE_VALUE_QUARTERS[base] }))
|
|
86
|
+
);
|
|
87
|
+
function assertPositiveBeats(beats, name) {
|
|
88
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, beats, name);
|
|
89
|
+
if (beats <= 0) {
|
|
90
|
+
throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name} must be positive; received ${beats}`);
|
|
91
|
+
}
|
|
92
|
+
return beats;
|
|
93
|
+
}
|
|
94
|
+
function durationToBeats(duration, options = {}) {
|
|
95
|
+
const quarters = quartersOf(assertDuration(duration, "duration"));
|
|
96
|
+
return quarters / beatQuarters(options);
|
|
97
|
+
}
|
|
98
|
+
function beatsToDuration(beats, options = {}) {
|
|
99
|
+
assertPositiveBeats(beats, "beats");
|
|
100
|
+
const spelled = spellQuarters(beats * beatQuarters(options));
|
|
101
|
+
if (spelled === void 0) {
|
|
102
|
+
throw new (0, _chunkQ2WGKJNNcjs.NoSolutionError)(`no single note value spells ${beats} beats`);
|
|
103
|
+
}
|
|
104
|
+
return spelled;
|
|
105
|
+
}
|
|
106
|
+
function beatsToTiedDurations(beats, options = {}) {
|
|
107
|
+
assertPositiveBeats(beats, "beats");
|
|
108
|
+
const quarters = beats * beatQuarters(options);
|
|
109
|
+
const single = spellQuarters(quarters);
|
|
110
|
+
if (single !== void 0) {
|
|
111
|
+
return [single];
|
|
112
|
+
}
|
|
113
|
+
_chunkQ2WGKJNNcjs.assertGenerationBudget.call(void 0,
|
|
114
|
+
Math.ceil(quarters / NOTE_VALUE_QUARTERS.whole) + PLAIN_VALUES.length,
|
|
115
|
+
"tie chain length"
|
|
116
|
+
);
|
|
117
|
+
const chain = [];
|
|
118
|
+
let remaining = quarters;
|
|
119
|
+
let previousQuarters = 0;
|
|
120
|
+
while (remaining > EPS) {
|
|
121
|
+
const next = PLAIN_VALUES.find((candidate) => candidate.quarters <= remaining + EPS);
|
|
122
|
+
if (next === void 0) {
|
|
123
|
+
throw new (0, _chunkQ2WGKJNNcjs.NoSolutionError)(`no chain of tied note values sums to ${beats} beats`);
|
|
124
|
+
}
|
|
125
|
+
const last = chain[chain.length - 1];
|
|
126
|
+
if (last !== void 0 && last.dots < MAX_SPELLED_DOTS && Math.abs(next.quarters * 2 - previousQuarters) <= EPS) {
|
|
127
|
+
last.dots += 1;
|
|
128
|
+
} else {
|
|
129
|
+
chain.push({ base: next.base, dots: 0 });
|
|
130
|
+
}
|
|
131
|
+
previousQuarters = next.quarters;
|
|
132
|
+
remaining -= next.quarters;
|
|
133
|
+
}
|
|
134
|
+
return chain;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/core/tempo/index.ts
|
|
138
|
+
var SECONDS_PER_MINUTE = 60;
|
|
139
|
+
var MAX_BPM = 1e3;
|
|
140
|
+
function assertTempoMap(map, name = "tempo map") {
|
|
141
|
+
if (!Array.isArray(map) || map.length === 0) {
|
|
142
|
+
throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name} must hold at least one tempo event`);
|
|
143
|
+
}
|
|
144
|
+
for (let index = 0; index < map.length; index += 1) {
|
|
145
|
+
const event = map[index];
|
|
146
|
+
if (event === void 0) {
|
|
147
|
+
throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name}[${index}] must be a tempo event; received undefined`);
|
|
148
|
+
}
|
|
149
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, event.startBeat, `${name}[${index}].startBeat`);
|
|
150
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, event.bpm, Number.MIN_VALUE, MAX_BPM, `${name}[${index}].bpm`);
|
|
151
|
+
const previous = map[index - 1];
|
|
152
|
+
if (previous !== void 0 && event.startBeat <= previous.startBeat) {
|
|
153
|
+
throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
|
|
154
|
+
`${name} must be sorted by startBeat with no repeats; [${index}].startBeat ${event.startBeat} does not follow ${previous.startBeat}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return map;
|
|
159
|
+
}
|
|
160
|
+
function firstEvent(map) {
|
|
161
|
+
const first = map[0];
|
|
162
|
+
if (first === void 0) {
|
|
163
|
+
throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("tempo map must hold at least one tempo event");
|
|
164
|
+
}
|
|
165
|
+
return first;
|
|
166
|
+
}
|
|
167
|
+
function integrateSeconds(map, fromBeat, toBeat) {
|
|
168
|
+
if (toBeat < fromBeat) {
|
|
169
|
+
return -integrateSeconds(map, toBeat, fromBeat);
|
|
170
|
+
}
|
|
171
|
+
let seconds = 0;
|
|
172
|
+
for (let index = 0; index < map.length; index += 1) {
|
|
173
|
+
const event = map[index];
|
|
174
|
+
if (event === void 0) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const segmentStart = index === 0 ? Number.NEGATIVE_INFINITY : event.startBeat;
|
|
178
|
+
const segmentEnd = _nullishCoalesce(_optionalChain([map, 'access', _ => _[index + 1], 'optionalAccess', _2 => _2.startBeat]), () => ( Number.POSITIVE_INFINITY));
|
|
179
|
+
const low = Math.max(fromBeat, segmentStart);
|
|
180
|
+
const high = Math.min(toBeat, segmentEnd);
|
|
181
|
+
if (high > low) {
|
|
182
|
+
seconds += (high - low) * SECONDS_PER_MINUTE / event.bpm;
|
|
183
|
+
}
|
|
184
|
+
if (toBeat <= segmentEnd) {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return seconds;
|
|
189
|
+
}
|
|
190
|
+
function beatsToSeconds(beat, map) {
|
|
191
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, beat, "beat");
|
|
192
|
+
assertTempoMap(map);
|
|
193
|
+
return integrateSeconds(map, firstEvent(map).startBeat, beat);
|
|
194
|
+
}
|
|
195
|
+
function secondsToBeats(seconds, map) {
|
|
196
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, seconds, "seconds");
|
|
197
|
+
assertTempoMap(map);
|
|
198
|
+
const origin = firstEvent(map);
|
|
199
|
+
if (seconds < 0) {
|
|
200
|
+
return origin.startBeat + seconds * origin.bpm / SECONDS_PER_MINUTE;
|
|
201
|
+
}
|
|
202
|
+
let remaining = seconds;
|
|
203
|
+
for (let index = 0; index < map.length - 1; index += 1) {
|
|
204
|
+
const event = map[index];
|
|
205
|
+
const next = map[index + 1];
|
|
206
|
+
if (event === void 0 || next === void 0) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const spanSeconds = (next.startBeat - event.startBeat) * SECONDS_PER_MINUTE / event.bpm;
|
|
210
|
+
if (remaining < spanSeconds) {
|
|
211
|
+
return event.startBeat + remaining * event.bpm / SECONDS_PER_MINUTE;
|
|
212
|
+
}
|
|
213
|
+
remaining -= spanSeconds;
|
|
214
|
+
}
|
|
215
|
+
const last = map[map.length - 1];
|
|
216
|
+
if (last === void 0) {
|
|
217
|
+
throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("tempo map must hold at least one tempo event");
|
|
218
|
+
}
|
|
219
|
+
return last.startBeat + remaining * last.bpm / SECONDS_PER_MINUTE;
|
|
220
|
+
}
|
|
221
|
+
function durationToSeconds(startBeat, lengthBeats, map) {
|
|
222
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, startBeat, "startBeat");
|
|
223
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, lengthBeats, 0, Number.MAX_SAFE_INTEGER, "lengthBeats");
|
|
224
|
+
assertTempoMap(map);
|
|
225
|
+
return integrateSeconds(map, startBeat, startBeat + lengthBeats);
|
|
226
|
+
}
|
|
227
|
+
function tempoAt(beat, map) {
|
|
228
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, beat, "beat");
|
|
229
|
+
assertTempoMap(map);
|
|
230
|
+
let bpm = firstEvent(map).bpm;
|
|
231
|
+
for (const event of map) {
|
|
232
|
+
if (event.startBeat > beat) {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
bpm = event.bpm;
|
|
236
|
+
}
|
|
237
|
+
return bpm;
|
|
238
|
+
}
|
|
239
|
+
function beatsToTicks(beat, ppq) {
|
|
240
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, beat, "beat");
|
|
241
|
+
_chunkQ2WGKJNNcjs.assertPositiveInt.call(void 0, ppq, "ppq");
|
|
242
|
+
return Math.round(beat * ppq);
|
|
243
|
+
}
|
|
244
|
+
function ticksToBeats(ticks, ppq) {
|
|
245
|
+
_chunkQ2WGKJNNcjs.assertInteger.call(void 0, ticks, "ticks");
|
|
246
|
+
_chunkQ2WGKJNNcjs.assertPositiveInt.call(void 0, ppq, "ppq");
|
|
247
|
+
return ticks / ppq;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/core/tuning/index.ts
|
|
251
|
+
var TWELVE_TET = Object.freeze({ refStep: 69, refFreq: 440, divisions: 12 });
|
|
252
|
+
function assertTuning(tuning) {
|
|
253
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, tuning.refStep, "tuning.refStep");
|
|
254
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, tuning.refFreq, Number.MIN_VALUE, Number.MAX_VALUE, "tuning.refFreq");
|
|
255
|
+
_chunkQ2WGKJNNcjs.assertPositiveInt.call(void 0, tuning.divisions, "tuning.divisions");
|
|
256
|
+
return tuning;
|
|
257
|
+
}
|
|
258
|
+
function edo(n, refFreq = 440, refStep = 69) {
|
|
259
|
+
_chunkQ2WGKJNNcjs.assertPositiveInt.call(void 0, n, "EDO divisions");
|
|
260
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, refFreq, Number.MIN_VALUE, Number.MAX_VALUE, "reference frequency");
|
|
261
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, refStep, "reference step");
|
|
262
|
+
return { refStep, refFreq, divisions: n };
|
|
263
|
+
}
|
|
264
|
+
function frequencyOf(step, tuning = TWELVE_TET) {
|
|
265
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, step, "step");
|
|
266
|
+
assertTuning(tuning);
|
|
267
|
+
const result = tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);
|
|
268
|
+
return _chunkQ2WGKJNNcjs.assertRange.call(void 0, result, Number.MIN_VALUE, Number.MAX_VALUE, "frequency result");
|
|
269
|
+
}
|
|
270
|
+
function nearestStep(freq, tuning = TWELVE_TET) {
|
|
271
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, freq, Number.MIN_VALUE, Number.MAX_VALUE, "frequency");
|
|
272
|
+
assertTuning(tuning);
|
|
273
|
+
return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));
|
|
274
|
+
}
|
|
275
|
+
function stepOf(freq, tuning = TWELVE_TET) {
|
|
276
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, freq, Number.MIN_VALUE, Number.MAX_VALUE, "frequency");
|
|
277
|
+
assertTuning(tuning);
|
|
278
|
+
return tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq);
|
|
279
|
+
}
|
|
280
|
+
function centsFromNearestStep(freq, tuning = TWELVE_TET) {
|
|
281
|
+
const exact = stepOf(freq, tuning);
|
|
282
|
+
return centsOfSteps(exact - Math.round(exact), tuning);
|
|
283
|
+
}
|
|
284
|
+
function stepsOfCents(cents, tuning = TWELVE_TET) {
|
|
285
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, cents, "cents");
|
|
286
|
+
assertTuning(tuning);
|
|
287
|
+
return _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, cents * tuning.divisions / 1200, "steps result");
|
|
288
|
+
}
|
|
289
|
+
function centsToRatio(cents) {
|
|
290
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, cents, "cents");
|
|
291
|
+
return 2 ** (cents / 1200);
|
|
292
|
+
}
|
|
293
|
+
function centsBetweenFreq(a, b) {
|
|
294
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, a, Number.MIN_VALUE, Number.MAX_VALUE, "first frequency");
|
|
295
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, b, Number.MIN_VALUE, Number.MAX_VALUE, "second frequency");
|
|
296
|
+
return 1200 * (Math.log2(b) - Math.log2(a));
|
|
297
|
+
}
|
|
298
|
+
function centsOfSteps(steps, tuning = TWELVE_TET) {
|
|
299
|
+
_chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, steps, "steps");
|
|
300
|
+
assertTuning(tuning);
|
|
301
|
+
return _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, steps * 1200 / tuning.divisions, "cents result");
|
|
302
|
+
}
|
|
303
|
+
function ratioToCents(numerator, denominator) {
|
|
304
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, numerator, Number.MIN_VALUE, Number.MAX_VALUE, "ratio numerator");
|
|
305
|
+
_chunkQ2WGKJNNcjs.assertRange.call(void 0, denominator, Number.MIN_VALUE, Number.MAX_VALUE, "ratio denominator");
|
|
306
|
+
return 1200 * (Math.log2(numerator) - Math.log2(denominator));
|
|
307
|
+
}
|
|
308
|
+
function justRatio(numerator, denominator) {
|
|
309
|
+
return Object.freeze([numerator, denominator]);
|
|
310
|
+
}
|
|
311
|
+
var JUST_RATIOS = Object.freeze({
|
|
312
|
+
0: justRatio(1, 1),
|
|
313
|
+
1: justRatio(16, 15),
|
|
314
|
+
2: justRatio(9, 8),
|
|
315
|
+
3: justRatio(6, 5),
|
|
316
|
+
4: justRatio(5, 4),
|
|
317
|
+
5: justRatio(4, 3),
|
|
318
|
+
6: justRatio(45, 32),
|
|
319
|
+
7: justRatio(3, 2),
|
|
320
|
+
8: justRatio(8, 5),
|
|
321
|
+
9: justRatio(5, 3),
|
|
322
|
+
10: justRatio(9, 5),
|
|
323
|
+
11: justRatio(15, 8),
|
|
324
|
+
12: justRatio(2, 1)
|
|
325
|
+
});
|
|
326
|
+
function justDeviationCents(semitoneClass) {
|
|
327
|
+
_chunkQ2WGKJNNcjs.assertInteger.call(void 0, semitoneClass, "semitone class", 0, 12);
|
|
328
|
+
const ratio = JUST_RATIOS[semitoneClass];
|
|
329
|
+
if (!ratio) {
|
|
330
|
+
throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`semitone class has no just ratio: ${semitoneClass}`);
|
|
331
|
+
}
|
|
332
|
+
return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
exports.NOTE_VALUES = NOTE_VALUES; exports.durationToBeats = durationToBeats; exports.beatsToDuration = beatsToDuration; exports.beatsToTiedDurations = beatsToTiedDurations; exports.beatsToSeconds = beatsToSeconds; exports.secondsToBeats = secondsToBeats; exports.durationToSeconds = durationToSeconds; exports.tempoAt = tempoAt; exports.beatsToTicks = beatsToTicks; exports.ticksToBeats = ticksToBeats; 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;
|
|
360
|
+
//# sourceMappingURL=chunk-NLEU4QSE.cjs.map
|