@libraz/libcantus 1.0.1 → 1.2.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 +27 -65
- package/README_ja.md +28 -66
- package/dist/analyze/index.cjs +11 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +79 -144
- package/dist/analyze/index.d.ts +79 -144
- package/dist/analyze/index.js +20 -16
- package/dist/chunk-35NBV2FT.cjs +145 -0
- package/dist/chunk-35NBV2FT.cjs.map +1 -0
- package/dist/{chunk-UADYAYAT.cjs → chunk-3R3QVKY5.cjs} +1353 -580
- package/dist/chunk-3R3QVKY5.cjs.map +1 -0
- package/dist/{chunk-WWURE4XT.cjs → chunk-76ZBTRG2.cjs} +624 -729
- package/dist/chunk-76ZBTRG2.cjs.map +1 -0
- package/dist/{chunk-FVSI36UV.js → chunk-7KG3RTYS.js} +197 -110
- package/dist/chunk-7KG3RTYS.js.map +1 -0
- package/dist/{chunk-VTI6FOLB.js → chunk-AGOVW7U6.js} +709 -133
- package/dist/chunk-AGOVW7U6.js.map +1 -0
- package/dist/{chunk-E3F4AHAH.cjs → chunk-CGBADIO5.cjs} +221 -134
- package/dist/chunk-CGBADIO5.cjs.map +1 -0
- package/dist/{chunk-6XH6QZYQ.cjs → chunk-DSMJRXCZ.cjs} +1455 -849
- package/dist/chunk-DSMJRXCZ.cjs.map +1 -0
- package/dist/{chunk-PKFNUHDY.js → chunk-FBH75DAY.js} +1218 -612
- package/dist/chunk-FBH75DAY.js.map +1 -0
- package/dist/chunk-H2KJHBW4.js +3329 -0
- package/dist/chunk-H2KJHBW4.js.map +1 -0
- package/dist/chunk-HPVSH7SF.cjs +2294 -0
- package/dist/chunk-HPVSH7SF.cjs.map +1 -0
- package/dist/{chunk-MXO72PLY.cjs → chunk-HWHGOVO6.cjs} +21 -19
- package/dist/chunk-HWHGOVO6.cjs.map +1 -0
- package/dist/chunk-HYJJ2V2I.js +2294 -0
- package/dist/chunk-HYJJ2V2I.js.map +1 -0
- package/dist/{chunk-AFM2ULJH.cjs → chunk-J7FHVZII.cjs} +1567 -1067
- package/dist/chunk-J7FHVZII.cjs.map +1 -0
- package/dist/chunk-K6OZXIR6.js +1 -0
- package/dist/{chunk-H3VORETI.js → chunk-M5RYMAFI.js} +487 -592
- package/dist/chunk-M5RYMAFI.js.map +1 -0
- package/dist/chunk-NB25P3EH.js +363 -0
- package/dist/chunk-NB25P3EH.js.map +1 -0
- package/dist/{chunk-TTCLJX6D.js → chunk-NFJ4P6ZZ.js} +10 -8
- package/dist/chunk-NFJ4P6ZZ.js.map +1 -0
- package/dist/chunk-NM7UAMC3.cjs +457 -0
- package/dist/chunk-NM7UAMC3.cjs.map +1 -0
- package/dist/chunk-PM4XJE2Y.cjs +363 -0
- package/dist/chunk-PM4XJE2Y.cjs.map +1 -0
- package/dist/chunk-PUKJP2Z5.js +145 -0
- package/dist/chunk-PUKJP2Z5.js.map +1 -0
- package/dist/{chunk-VOHZB45S.js → chunk-Q6LFSAKV.js} +1321 -821
- package/dist/chunk-Q6LFSAKV.js.map +1 -0
- package/dist/{chunk-INHVXP6Z.js → chunk-RFIBF2RR.js} +1203 -430
- package/dist/chunk-RFIBF2RR.js.map +1 -0
- package/dist/{chunk-Q2WGKJNN.cjs → chunk-S5BKZZGM.cjs} +699 -123
- package/dist/chunk-S5BKZZGM.cjs.map +1 -0
- package/dist/chunk-SGLEQVJH.cjs +3329 -0
- package/dist/chunk-SGLEQVJH.cjs.map +1 -0
- package/dist/chunk-UGLFDG7L.cjs +1 -0
- package/dist/chunk-UGLFDG7L.cjs.map +1 -0
- package/dist/{chunk-YH3BIKHH.js → chunk-UMWZ4ZET.js} +146 -49
- package/dist/chunk-UMWZ4ZET.js.map +1 -0
- package/dist/{index-szBv3496.d.ts → coerce-DP0vYu-x.d.ts} +286 -199
- package/dist/{index-C_8o--VE.d.cts → coerce-DRERzdve.d.cts} +286 -199
- package/dist/core/index.cjs +9 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +122 -108
- package/dist/core/index.d.ts +122 -108
- package/dist/core/index.js +32 -28
- package/dist/generate/index.cjs +8 -8
- package/dist/generate/index.d.cts +168 -84
- package/dist/generate/index.d.ts +168 -84
- package/dist/generate/index.js +7 -7
- package/dist/{index-CGpRv5sl.d.cts → index-9eoQs2C1.d.ts} +34 -19
- package/dist/{index-CCNJRG-P.d.cts → index-B9QLziDe.d.cts} +293 -56
- package/dist/{index-Dzr13q3d.d.cts → index-BBWNZS0f.d.cts} +135 -58
- package/dist/{index-9wcMhG73.d.ts → index-BHIUyjhZ.d.ts} +293 -56
- package/dist/{index-BGFW-8P2.d.cts → index-BJgP97N1.d.ts} +196 -114
- package/dist/{index-CunYKLiu.d.ts → index-BL5N2Oyz.d.cts} +1 -1
- package/dist/{index-Cj49F892.d.ts → index-BMweUlm5.d.cts} +63 -23
- package/dist/{index-CgHqxdps.d.ts → index-BNSM-DSk.d.cts} +196 -114
- package/dist/{index-D57QRbVn.d.ts → index-BRMbPkHE.d.ts} +480 -254
- package/dist/{index-Chbst3Jx.d.ts → index-CJBBiAmU.d.ts} +135 -58
- package/dist/{index-BkGmlyI3.d.cts → index-CL4b4gUQ.d.ts} +63 -23
- package/dist/{index-B9MNZboQ.d.ts → index-Cy15oTvh.d.cts} +52 -22
- package/dist/{index-B9MNZboQ.d.cts → index-Cy15oTvh.d.ts} +52 -22
- package/dist/{index-BO6xE9YU.d.cts → index-DGCNKAUr.d.cts} +480 -254
- package/dist/{index-B4o55NDr.d.cts → index-Dw2DP7vm.d.ts} +1 -1
- package/dist/{index-G1uwGGWQ.d.ts → index-cHvbYgJI.d.cts} +34 -19
- package/dist/index.cjs +42 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +82 -54
- package/dist/{masks-DYbE485J.d.cts → masks-Cc7-MzzY.d.ts} +49 -2
- package/dist/{masks-DYbE485J.d.ts → masks-pADfTOiD.d.cts} +49 -2
- package/dist/model/index.cjs +19 -12
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +504 -182
- package/dist/model/index.d.ts +504 -182
- package/dist/model/index.js +18 -11
- package/dist/{positional-Cptlj-xs.d.ts → positional-DJgs6ltD.d.ts} +85 -8
- package/dist/{positional-DvH_sd8u.d.cts → positional-DMZer-FM.d.cts} +85 -8
- package/dist/theory/index.cjs +35 -6
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +257 -62
- package/dist/theory/index.d.ts +257 -62
- package/dist/theory/index.js +41 -12
- package/package.json +7 -5
- package/dist/chunk-2LCPM3EG.js +0 -3224
- package/dist/chunk-2LCPM3EG.js.map +0 -1
- package/dist/chunk-6XH6QZYQ.cjs.map +0 -1
- package/dist/chunk-6XPTBE7Y.cjs +0 -49
- package/dist/chunk-6XPTBE7Y.cjs.map +0 -1
- package/dist/chunk-AFM2ULJH.cjs.map +0 -1
- package/dist/chunk-B6XNKWZV.cjs +0 -3224
- package/dist/chunk-B6XNKWZV.cjs.map +0 -1
- package/dist/chunk-E3F4AHAH.cjs.map +0 -1
- package/dist/chunk-ELCTZUJJ.js +0 -379
- package/dist/chunk-ELCTZUJJ.js.map +0 -1
- package/dist/chunk-FVSI36UV.js.map +0 -1
- package/dist/chunk-H3VORETI.js.map +0 -1
- package/dist/chunk-H42UHPYW.js +0 -1577
- package/dist/chunk-H42UHPYW.js.map +0 -1
- package/dist/chunk-HFPS7JFD.js +0 -1
- package/dist/chunk-INHVXP6Z.js.map +0 -1
- package/dist/chunk-K4ZJUGF4.cjs +0 -379
- package/dist/chunk-K4ZJUGF4.cjs.map +0 -1
- package/dist/chunk-KNJEKLKV.js +0 -49
- package/dist/chunk-KNJEKLKV.js.map +0 -1
- package/dist/chunk-MXO72PLY.cjs.map +0 -1
- package/dist/chunk-NLEU4QSE.cjs +0 -360
- package/dist/chunk-NLEU4QSE.cjs.map +0 -1
- package/dist/chunk-OE6WK2Y2.cjs +0 -1577
- package/dist/chunk-OE6WK2Y2.cjs.map +0 -1
- package/dist/chunk-PKFNUHDY.js.map +0 -1
- package/dist/chunk-Q2WGKJNN.cjs.map +0 -1
- package/dist/chunk-RHIJ3S4I.cjs +0 -1
- package/dist/chunk-RHIJ3S4I.cjs.map +0 -1
- package/dist/chunk-TTCLJX6D.js.map +0 -1
- package/dist/chunk-UADYAYAT.cjs.map +0 -1
- package/dist/chunk-VOHZB45S.js.map +0 -1
- package/dist/chunk-VTI6FOLB.js.map +0 -1
- package/dist/chunk-WWURE4XT.cjs.map +0 -1
- package/dist/chunk-YH3BIKHH.js.map +0 -1
- /package/dist/{chunk-HFPS7JFD.js.map → chunk-K6OZXIR6.js.map} +0 -0
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ARTICULATIONS,
|
|
3
|
+
BEAT_EPS,
|
|
2
4
|
InvalidInputError,
|
|
3
5
|
assertFiniteNumber,
|
|
4
6
|
assertInteger,
|
|
7
|
+
assertMidiPitch,
|
|
5
8
|
assertNoteEvents,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"accent",
|
|
12
|
-
"ghost",
|
|
13
|
-
"staccato",
|
|
14
|
-
"legato",
|
|
15
|
-
"slide",
|
|
16
|
-
"hammer",
|
|
17
|
-
"mute",
|
|
18
|
-
"flam",
|
|
19
|
-
"drag",
|
|
20
|
-
"roll",
|
|
21
|
-
"choke",
|
|
22
|
-
"open"
|
|
23
|
-
]);
|
|
9
|
+
assertOneOf,
|
|
10
|
+
assertPositiveInt,
|
|
11
|
+
assertRange,
|
|
12
|
+
describeRejected
|
|
13
|
+
} from "./chunk-AGOVW7U6.js";
|
|
24
14
|
|
|
25
15
|
// src/core/instrument/profile.ts
|
|
26
16
|
var LIMBS = Object.freeze(["rightHand", "leftHand", "rightFoot", "leftFoot"]);
|
|
@@ -47,19 +37,55 @@ function toStringedProfile(value, name = "instrument") {
|
|
|
47
37
|
}
|
|
48
38
|
return profile;
|
|
49
39
|
}
|
|
40
|
+
var MAX_FRETS = 127;
|
|
41
|
+
var MAX_POLYPHONY = 128;
|
|
50
42
|
function assertProfile(profile) {
|
|
43
|
+
if (typeof profile.name !== "string" || profile.name === "") {
|
|
44
|
+
throw new InvalidInputError(
|
|
45
|
+
`instrument name must be a non-empty string; received ${describeRejected(profile.name)}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(profile.articulations)) {
|
|
49
|
+
throw new InvalidInputError(
|
|
50
|
+
`${profile.name} articulations must be an array; received ${describeRejected(profile.articulations)}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
for (const [index, articulation] of profile.articulations.entries()) {
|
|
54
|
+
assertOneOf(articulation, ARTICULATIONS, `${profile.name} articulations[${index}]`);
|
|
55
|
+
}
|
|
56
|
+
assertPositiveInt(profile.polyphony, `${profile.name} polyphony`, MAX_POLYPHONY);
|
|
51
57
|
if (profile.kind === "stringed") {
|
|
52
|
-
if (profile.tuning.length === 0) {
|
|
58
|
+
if (!Array.isArray(profile.tuning) || profile.tuning.length === 0) {
|
|
53
59
|
throw new InvalidInputError(`${profile.name} must have at least one string`);
|
|
54
60
|
}
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
for (const [index, open] of profile.tuning.entries()) {
|
|
62
|
+
assertMidiPitch(open, `${profile.name} tuning[${index}]`);
|
|
57
63
|
}
|
|
64
|
+
assertInteger(profile.frets, `${profile.name} frets`, 0, MAX_FRETS);
|
|
65
|
+
assertInteger(profile.maxStretch, `${profile.name} maxStretch`, 0, MAX_FRETS);
|
|
58
66
|
return;
|
|
59
67
|
}
|
|
60
|
-
if (
|
|
68
|
+
if (!Array.isArray(profile.limbs) || profile.limbs.length === 0) {
|
|
69
|
+
throw new InvalidInputError(`${profile.name} must have at least one limb`);
|
|
70
|
+
}
|
|
71
|
+
for (const [index, limb] of profile.limbs.entries()) {
|
|
72
|
+
assertOneOf(limb, LIMBS, `${profile.name} limbs[${index}]`);
|
|
73
|
+
}
|
|
74
|
+
if (typeof profile.reach !== "object" || profile.reach === null) {
|
|
75
|
+
throw new InvalidInputError(
|
|
76
|
+
`${profile.name} reach must be a table of limbs by voice; received ${describeRejected(profile.reach)}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const voices = Object.keys(profile.reach);
|
|
80
|
+
for (const key of voices) {
|
|
81
|
+
assertMidiPitch(Number(key), `${profile.name} reach pitch`);
|
|
82
|
+
}
|
|
83
|
+
if (voices.every((key) => reachOf(profile, Number(key)).length === 0)) {
|
|
61
84
|
throw new InvalidInputError(`${profile.name} must have at least one voice within reach`);
|
|
62
85
|
}
|
|
86
|
+
for (const pitch of profile.overdub ?? []) {
|
|
87
|
+
assertMidiPitch(pitch, `${profile.name} overdub pitch`);
|
|
88
|
+
}
|
|
63
89
|
}
|
|
64
90
|
function reachOf(profile, pitch) {
|
|
65
91
|
if (pitch === void 0) {
|
|
@@ -68,7 +94,9 @@ function reachOf(profile, pitch) {
|
|
|
68
94
|
return (profile.reach[pitch] ?? []).filter((limb) => profile.limbs.includes(limb));
|
|
69
95
|
}
|
|
70
96
|
function instrumentRange(profile) {
|
|
71
|
-
|
|
97
|
+
return rangeIn(toInstrumentProfile(profile));
|
|
98
|
+
}
|
|
99
|
+
function rangeIn(profile) {
|
|
72
100
|
if (profile.kind === "stringed") {
|
|
73
101
|
let low2 = Number.POSITIVE_INFINITY;
|
|
74
102
|
let high2 = Number.NEGATIVE_INFINITY;
|
|
@@ -91,43 +119,56 @@ function instrumentRange(profile) {
|
|
|
91
119
|
return { low, high };
|
|
92
120
|
}
|
|
93
121
|
function canSound(profile, pitch) {
|
|
94
|
-
|
|
122
|
+
const resolved = toInstrumentProfile(profile, "instrument");
|
|
123
|
+
assertMidiPitch(pitch, "pitch");
|
|
124
|
+
return soundsIn(resolved, pitch);
|
|
125
|
+
}
|
|
126
|
+
function soundsIn(profile, pitch) {
|
|
95
127
|
if (profile.kind === "stringed") {
|
|
96
128
|
return profile.tuning.some((open) => pitch >= open && pitch - open <= profile.frets);
|
|
97
129
|
}
|
|
98
130
|
return reachOf(profile, pitch).length > 0;
|
|
99
131
|
}
|
|
100
132
|
function fingeringsFor(profile, pitch) {
|
|
101
|
-
|
|
133
|
+
const stringed = toStringedProfile(profile, "instrument");
|
|
134
|
+
assertMidiPitch(pitch, "pitch");
|
|
135
|
+
return fingeringsIn(stringed, pitch);
|
|
136
|
+
}
|
|
137
|
+
function fingeringsIn(stringed, pitch) {
|
|
102
138
|
const out = [];
|
|
103
|
-
for (let string = 0; string <
|
|
104
|
-
const open =
|
|
139
|
+
for (let string = 0; string < stringed.tuning.length; string += 1) {
|
|
140
|
+
const open = stringed.tuning[string];
|
|
105
141
|
if (open === void 0) {
|
|
106
142
|
continue;
|
|
107
143
|
}
|
|
108
144
|
const fret = pitch - open;
|
|
109
|
-
if (fret >= 0 && fret <=
|
|
145
|
+
if (fret >= 0 && fret <= stringed.frets) {
|
|
110
146
|
out.push({ string, fret });
|
|
111
147
|
}
|
|
112
148
|
}
|
|
113
149
|
return out;
|
|
114
150
|
}
|
|
115
151
|
function foldIntoRange(pitch, profile) {
|
|
116
|
-
|
|
152
|
+
const resolved = toInstrumentProfile(profile, "instrument");
|
|
153
|
+
assertMidiPitch(pitch, "pitch");
|
|
154
|
+
return foldIn(pitch, resolved);
|
|
155
|
+
}
|
|
156
|
+
function foldIn(pitch, resolved) {
|
|
157
|
+
if (soundsIn(resolved, pitch)) {
|
|
117
158
|
return pitch;
|
|
118
159
|
}
|
|
119
|
-
const { low, high } =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
160
|
+
const { low, high } = rangeIn(resolved);
|
|
161
|
+
const first = pitch + 12 * Math.ceil((low - pitch) / 12);
|
|
162
|
+
let best;
|
|
163
|
+
for (let candidate = first; candidate <= high; candidate += 12) {
|
|
164
|
+
if (!soundsIn(resolved, candidate)) {
|
|
165
|
+
continue;
|
|
123
166
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (canSound(profile, candidate)) {
|
|
127
|
-
return candidate;
|
|
167
|
+
if (best === void 0 || Math.abs(candidate - pitch) <= Math.abs(best - pitch)) {
|
|
168
|
+
best = candidate;
|
|
128
169
|
}
|
|
129
170
|
}
|
|
130
|
-
return pitch;
|
|
171
|
+
return best ?? pitch;
|
|
131
172
|
}
|
|
132
173
|
|
|
133
174
|
// src/core/instrument/playability.ts
|
|
@@ -138,13 +179,13 @@ var DIFFICULTY_SCALE = 12;
|
|
|
138
179
|
var MIN_DIFFICULTY = 1;
|
|
139
180
|
var MAX_DIFFICULTY = 5;
|
|
140
181
|
var STRING_CROSSING_COST = 2;
|
|
141
|
-
var
|
|
142
|
-
var EPS = 1e-9;
|
|
182
|
+
var SIMULTANEITY_BEATS = 1 / 128;
|
|
143
183
|
function playability(notes, profile, bpm) {
|
|
144
184
|
assertNoteEvents(notes, "playability notes", { allowNonPositiveDuration: true });
|
|
145
185
|
if (bpm !== void 0) {
|
|
146
186
|
assertRange(bpm, Number.MIN_VALUE, 1e3, "playability bpm");
|
|
147
187
|
}
|
|
188
|
+
const instrument = toInstrumentProfile(profile, "playability profile");
|
|
148
189
|
const order = notes.map((_, index) => index).sort((a, b) => {
|
|
149
190
|
const left = notes[a];
|
|
150
191
|
const right = notes[b];
|
|
@@ -161,11 +202,13 @@ function playability(notes, profile, bpm) {
|
|
|
161
202
|
movement: 0,
|
|
162
203
|
secondsPerBeat: bpm === void 0 ? void 0 : 60 / bpm
|
|
163
204
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
placeOnNeck(state,
|
|
205
|
+
if (instrument.kind === "stringed") {
|
|
206
|
+
checkExistence(state, instrument, (pitch) => soundsIn(instrument, pitch));
|
|
207
|
+
placeOnNeck(state, instrument);
|
|
167
208
|
} else {
|
|
168
|
-
|
|
209
|
+
const kit = readKit(instrument);
|
|
210
|
+
checkExistence(state, instrument, (pitch) => limbsFor(kit, pitch).length > 0);
|
|
211
|
+
placeOnKit(state, kit);
|
|
169
212
|
}
|
|
170
213
|
return {
|
|
171
214
|
difficulty: difficultyOf(state, bpm),
|
|
@@ -173,13 +216,13 @@ function playability(notes, profile, bpm) {
|
|
|
173
216
|
placements: state.placements
|
|
174
217
|
};
|
|
175
218
|
}
|
|
176
|
-
function checkExistence(state, profile) {
|
|
219
|
+
function checkExistence(state, profile, sounds) {
|
|
177
220
|
for (const index of state.order) {
|
|
178
221
|
const note = state.notes[index];
|
|
179
222
|
if (!note) {
|
|
180
223
|
continue;
|
|
181
224
|
}
|
|
182
|
-
if (!
|
|
225
|
+
if (!sounds(note.pitch)) {
|
|
183
226
|
state.issues.push({
|
|
184
227
|
type: "noteOutOfRange",
|
|
185
228
|
layer: 1,
|
|
@@ -202,33 +245,35 @@ function checkExistence(state, profile) {
|
|
|
202
245
|
}
|
|
203
246
|
}
|
|
204
247
|
}
|
|
248
|
+
function isSimultaneous(anchorBeat, startBeat) {
|
|
249
|
+
return startBeat - anchorBeat <= SIMULTANEITY_BEATS + BEAT_EPS;
|
|
250
|
+
}
|
|
205
251
|
function soundingGroups(state) {
|
|
206
252
|
const groups = [];
|
|
207
253
|
let active = [];
|
|
208
254
|
let cursor = 0;
|
|
209
|
-
let
|
|
255
|
+
let anchor;
|
|
210
256
|
for (const index of state.order) {
|
|
211
257
|
const note = state.notes[index];
|
|
212
258
|
if (!note) {
|
|
213
259
|
continue;
|
|
214
260
|
}
|
|
215
|
-
|
|
216
|
-
if (key === previousKey) {
|
|
261
|
+
if (anchor !== void 0 && isSimultaneous(anchor, note.startBeat)) {
|
|
217
262
|
continue;
|
|
218
263
|
}
|
|
219
|
-
previousKey = key;
|
|
220
264
|
const at = note.startBeat;
|
|
265
|
+
anchor = at;
|
|
221
266
|
active = active.filter((member) => {
|
|
222
267
|
const held = state.notes[member];
|
|
223
|
-
return held !== void 0 && held.startBeat + held.durationBeat > at +
|
|
268
|
+
return held !== void 0 && held.startBeat + held.durationBeat > at + BEAT_EPS;
|
|
224
269
|
});
|
|
225
270
|
while (cursor < state.order.length) {
|
|
226
271
|
const candidateIndex = state.order[cursor];
|
|
227
272
|
const candidate = candidateIndex === void 0 ? void 0 : state.notes[candidateIndex];
|
|
228
|
-
if (!candidate || candidate.startBeat
|
|
273
|
+
if (!candidate || !isSimultaneous(at, candidate.startBeat)) {
|
|
229
274
|
break;
|
|
230
275
|
}
|
|
231
|
-
if (candidateIndex !== void 0 && candidate.startBeat + candidate.durationBeat > at +
|
|
276
|
+
if (candidateIndex !== void 0 && candidate.startBeat + candidate.durationBeat > at + BEAT_EPS) {
|
|
232
277
|
active.push(candidateIndex);
|
|
233
278
|
}
|
|
234
279
|
cursor += 1;
|
|
@@ -248,12 +293,12 @@ function placeOnNeck(state, profile) {
|
|
|
248
293
|
if (!note || !placement) {
|
|
249
294
|
continue;
|
|
250
295
|
}
|
|
251
|
-
const candidates =
|
|
296
|
+
const candidates = fingeringsIn(profile, note.pitch);
|
|
252
297
|
if (candidates.length === 0) {
|
|
253
298
|
continue;
|
|
254
299
|
}
|
|
255
300
|
const free = candidates.filter(
|
|
256
|
-
(candidate) => (stringFreeAt[candidate.string] ?? 0) <= note.startBeat +
|
|
301
|
+
(candidate) => (stringFreeAt[candidate.string] ?? 0) <= note.startBeat + BEAT_EPS
|
|
257
302
|
);
|
|
258
303
|
if (free.length === 0) {
|
|
259
304
|
const blocked = candidates.map((candidate) => stringHeldBy[candidate.string]).filter((held) => held !== void 0);
|
|
@@ -304,7 +349,7 @@ function checkShiftSpeed(state, profile, fromIndex, toIndex, from, to) {
|
|
|
304
349
|
}
|
|
305
350
|
const seconds = (note.startBeat - previousNote.startBeat) * secondsPerBeat;
|
|
306
351
|
const shift = Math.abs(to.fret - from.fret);
|
|
307
|
-
if (seconds <=
|
|
352
|
+
if (seconds <= BEAT_EPS || shift === 0) {
|
|
308
353
|
return;
|
|
309
354
|
}
|
|
310
355
|
if (shift / seconds > MAX_FRET_SHIFT_PER_SECOND) {
|
|
@@ -350,61 +395,103 @@ function checkPolyphony(state, profile, group) {
|
|
|
350
395
|
message: `${group.members.length} notes sound at once; ${profile.name} has ${profile.polyphony}`
|
|
351
396
|
});
|
|
352
397
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const
|
|
398
|
+
var NO_LIMBS = Object.freeze([]);
|
|
399
|
+
function readKit(profile) {
|
|
400
|
+
const limbs = /* @__PURE__ */ new Map();
|
|
401
|
+
for (const key of Object.keys(profile.reach)) {
|
|
402
|
+
const pitch = Number(key);
|
|
403
|
+
limbs.set(pitch, reachOf(profile, pitch));
|
|
404
|
+
}
|
|
405
|
+
return { profile, limbs, overdub: new Set(profile.overdub ?? []) };
|
|
406
|
+
}
|
|
407
|
+
function limbsFor(kit, pitch) {
|
|
408
|
+
if (pitch === void 0) {
|
|
409
|
+
return NO_LIMBS;
|
|
410
|
+
}
|
|
411
|
+
return kit.limbs.get(pitch) ?? NO_LIMBS;
|
|
412
|
+
}
|
|
413
|
+
function passesAt(state, kit, members) {
|
|
414
|
+
const played = { id: "kit", members: [] };
|
|
415
|
+
const overdubs = /* @__PURE__ */ new Map();
|
|
416
|
+
for (const index of members) {
|
|
417
|
+
const pitch = state.notes[index]?.pitch;
|
|
418
|
+
if (pitch === void 0 || !kit.overdub.has(pitch)) {
|
|
419
|
+
played.members.push(index);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
let pass = overdubs.get(pitch);
|
|
423
|
+
if (!pass) {
|
|
424
|
+
pass = { id: `overdub ${pitch}`, members: [] };
|
|
425
|
+
overdubs.set(pitch, pass);
|
|
426
|
+
}
|
|
427
|
+
pass.members.push(index);
|
|
428
|
+
}
|
|
429
|
+
return [played, ...overdubs.values()];
|
|
430
|
+
}
|
|
431
|
+
function placePass(state, kit, pass, memory) {
|
|
432
|
+
const taken = /* @__PURE__ */ new Set();
|
|
433
|
+
let conflicted = false;
|
|
434
|
+
const members = [...pass.members].sort((a, b) => {
|
|
435
|
+
const left = limbsFor(kit, state.notes[a]?.pitch);
|
|
436
|
+
const right = limbsFor(kit, state.notes[b]?.pitch);
|
|
437
|
+
return left.length - right.length || a - b;
|
|
438
|
+
});
|
|
439
|
+
for (const index of members) {
|
|
440
|
+
const note = state.notes[index];
|
|
441
|
+
const placement = state.placements[index];
|
|
442
|
+
if (!note || !placement) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const candidates = limbsFor(kit, note.pitch);
|
|
446
|
+
if (candidates.length === 0) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const preferred = memory.limbForVoice.get(note.pitch);
|
|
450
|
+
const free = candidates.filter((limb) => !taken.has(limb));
|
|
451
|
+
const chosen = free.length === 0 ? candidates[0] : preferred !== void 0 && free.includes(preferred) ? preferred : free[0];
|
|
452
|
+
if (free.length === 0) {
|
|
453
|
+
conflicted = true;
|
|
454
|
+
}
|
|
455
|
+
if (chosen === void 0) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
taken.add(chosen);
|
|
459
|
+
placement.limb = chosen;
|
|
460
|
+
memory.limbForVoice.set(note.pitch, chosen);
|
|
461
|
+
state.movement += 1;
|
|
462
|
+
const strokeKey = `${pass.id} ${chosen}`;
|
|
463
|
+
const previousIndex = memory.lastStroke.get(strokeKey);
|
|
464
|
+
if (previousIndex !== void 0) {
|
|
465
|
+
const previousNote = state.notes[previousIndex];
|
|
466
|
+
if (previousNote && previousNote.pitch !== note.pitch) {
|
|
467
|
+
state.movement += 1;
|
|
468
|
+
}
|
|
469
|
+
checkStrokeSpeed(state, kit.profile, previousIndex, index, chosen);
|
|
470
|
+
}
|
|
471
|
+
memory.lastStroke.set(strokeKey, index);
|
|
472
|
+
}
|
|
473
|
+
return conflicted;
|
|
474
|
+
}
|
|
475
|
+
function placeOnKit(state, kit) {
|
|
476
|
+
const memory = { limbForVoice: /* @__PURE__ */ new Map(), lastStroke: /* @__PURE__ */ new Map() };
|
|
356
477
|
for (const group of onsetGroups(state)) {
|
|
357
|
-
const taken = /* @__PURE__ */ new Set();
|
|
358
478
|
let conflicted = false;
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
const right = reachOf(profile, state.notes[b]?.pitch);
|
|
362
|
-
return left.length - right.length || a - b;
|
|
363
|
-
});
|
|
364
|
-
for (const index of members) {
|
|
365
|
-
const note = state.notes[index];
|
|
366
|
-
const placement = state.placements[index];
|
|
367
|
-
if (!note || !placement) {
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
const candidates = reachOf(profile, note.pitch);
|
|
371
|
-
if (candidates.length === 0) {
|
|
479
|
+
for (const pass of passesAt(state, kit, group.members)) {
|
|
480
|
+
if (!placePass(state, kit, pass, memory)) {
|
|
372
481
|
continue;
|
|
373
482
|
}
|
|
374
|
-
|
|
375
|
-
const free = candidates.filter((limb) => !taken.has(limb));
|
|
376
|
-
const chosen = free.length === 0 ? candidates[0] : preferred !== void 0 && free.includes(preferred) ? preferred : free[0];
|
|
377
|
-
if (free.length === 0) {
|
|
378
|
-
conflicted = true;
|
|
379
|
-
}
|
|
380
|
-
if (chosen === void 0) {
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
taken.add(chosen);
|
|
384
|
-
placement.limb = chosen;
|
|
385
|
-
limbForVoice.set(note.pitch, chosen);
|
|
386
|
-
state.movement += 1;
|
|
387
|
-
const previousIndex = lastStroke.get(chosen);
|
|
388
|
-
if (previousIndex !== void 0) {
|
|
389
|
-
const previousNote = state.notes[previousIndex];
|
|
390
|
-
if (previousNote && previousNote.pitch !== note.pitch) {
|
|
391
|
-
state.movement += 1;
|
|
392
|
-
}
|
|
393
|
-
checkStrokeSpeed(state, profile, previousIndex, index, chosen);
|
|
394
|
-
}
|
|
395
|
-
lastStroke.set(chosen, index);
|
|
396
|
-
}
|
|
397
|
-
if (conflicted) {
|
|
483
|
+
conflicted = true;
|
|
398
484
|
state.issues.push({
|
|
399
485
|
type: "limbConflict",
|
|
400
486
|
layer: 2,
|
|
401
|
-
notes: [...
|
|
487
|
+
notes: [...pass.members].sort((a, b) => a - b),
|
|
402
488
|
startBeat: group.startBeat,
|
|
403
489
|
impossible: true,
|
|
404
|
-
message: `${
|
|
490
|
+
message: `${pass.members.length} simultaneous strokes cannot be shared among the limbs of ${kit.profile.name}`
|
|
405
491
|
});
|
|
406
|
-
}
|
|
407
|
-
|
|
492
|
+
}
|
|
493
|
+
if (!conflicted) {
|
|
494
|
+
checkPolyphony(state, kit.profile, group);
|
|
408
495
|
}
|
|
409
496
|
}
|
|
410
497
|
}
|
|
@@ -419,7 +506,7 @@ function checkStrokeSpeed(state, profile, fromIndex, toIndex, limb) {
|
|
|
419
506
|
return;
|
|
420
507
|
}
|
|
421
508
|
const seconds = (note.startBeat - previousNote.startBeat) * secondsPerBeat;
|
|
422
|
-
if (seconds <=
|
|
509
|
+
if (seconds <= BEAT_EPS || seconds >= MIN_LIMB_STROKE_SECONDS) {
|
|
423
510
|
return;
|
|
424
511
|
}
|
|
425
512
|
state.issues.push({
|
|
@@ -433,18 +520,15 @@ function checkStrokeSpeed(state, profile, fromIndex, toIndex, limb) {
|
|
|
433
520
|
}
|
|
434
521
|
function onsetGroups(state) {
|
|
435
522
|
const groups = [];
|
|
436
|
-
let key = Number.NaN;
|
|
437
523
|
let current;
|
|
438
524
|
for (const index of state.order) {
|
|
439
525
|
const note = state.notes[index];
|
|
440
|
-
if (!note) {
|
|
526
|
+
if (!note || note.durationBeat <= 0) {
|
|
441
527
|
continue;
|
|
442
528
|
}
|
|
443
|
-
|
|
444
|
-
if (!current || onsetKey !== key) {
|
|
529
|
+
if (!current || !isSimultaneous(current.startBeat, note.startBeat)) {
|
|
445
530
|
current = { startBeat: note.startBeat, members: [] };
|
|
446
531
|
groups.push(current);
|
|
447
|
-
key = onsetKey;
|
|
448
532
|
}
|
|
449
533
|
current.members.push(index);
|
|
450
534
|
}
|
|
@@ -461,7 +545,7 @@ function difficultyOf(state, bpm) {
|
|
|
461
545
|
last = Math.max(last, note.startBeat + Math.max(0, note.durationBeat));
|
|
462
546
|
}
|
|
463
547
|
const spanBeats = last - first;
|
|
464
|
-
if (!Number.isFinite(spanBeats) || spanBeats <=
|
|
548
|
+
if (!Number.isFinite(spanBeats) || spanBeats <= BEAT_EPS) {
|
|
465
549
|
return MIN_DIFFICULTY;
|
|
466
550
|
}
|
|
467
551
|
const seconds = spanBeats * 60 / (bpm ?? REFERENCE_BPM);
|
|
@@ -666,14 +750,17 @@ function createRng(seed) {
|
|
|
666
750
|
}
|
|
667
751
|
|
|
668
752
|
export {
|
|
669
|
-
ARTICULATIONS,
|
|
670
753
|
LIMBS,
|
|
671
754
|
toInstrumentProfile,
|
|
672
755
|
toStringedProfile,
|
|
673
756
|
instrumentRange,
|
|
757
|
+
rangeIn,
|
|
674
758
|
canSound,
|
|
759
|
+
soundsIn,
|
|
675
760
|
fingeringsFor,
|
|
761
|
+
fingeringsIn,
|
|
676
762
|
foldIntoRange,
|
|
763
|
+
foldIn,
|
|
677
764
|
playability,
|
|
678
765
|
BASS_4_STRING,
|
|
679
766
|
BASS_5_STRING,
|
|
@@ -687,4 +774,4 @@ export {
|
|
|
687
774
|
resolveAlgorithmVersion,
|
|
688
775
|
createRng
|
|
689
776
|
};
|
|
690
|
-
//# sourceMappingURL=chunk-
|
|
777
|
+
//# sourceMappingURL=chunk-7KG3RTYS.js.map
|