@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
|
@@ -40,8 +40,24 @@ function isLibcantusError(value) {
|
|
|
40
40
|
return code === "INVALID_INPUT" || code === "NO_SOLUTION" || code === "BUDGET_EXCEEDED";
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// src/core/instrument/articulation.ts
|
|
44
|
+
var ARTICULATIONS = Object.freeze([
|
|
45
|
+
"accent",
|
|
46
|
+
"ghost",
|
|
47
|
+
"staccato",
|
|
48
|
+
"legato",
|
|
49
|
+
"slide",
|
|
50
|
+
"hammer",
|
|
51
|
+
"mute",
|
|
52
|
+
"flam",
|
|
53
|
+
"drag",
|
|
54
|
+
"roll",
|
|
55
|
+
"choke",
|
|
56
|
+
"open"
|
|
57
|
+
]);
|
|
58
|
+
|
|
43
59
|
// src/core/meter/internal.ts
|
|
44
|
-
var
|
|
60
|
+
var BEAT_EPS = 1e-9;
|
|
45
61
|
function unitBeatsOf(ts) {
|
|
46
62
|
return 4 / ts.denominator;
|
|
47
63
|
}
|
|
@@ -96,86 +112,195 @@ function barBeatsOf(ts) {
|
|
|
96
112
|
function isMeterMap(meter) {
|
|
97
113
|
return Array.isArray(meter);
|
|
98
114
|
}
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
function copyTimeSignature(ts) {
|
|
116
|
+
return ts.grouping === void 0 ? { numerator: ts.numerator, denominator: ts.denominator } : { numerator: ts.numerator, denominator: ts.denominator, grouping: [...ts.grouping] };
|
|
117
|
+
}
|
|
118
|
+
function copyMeterMap(map) {
|
|
119
|
+
const copy = new Array(map.length);
|
|
120
|
+
for (let i = 0; i < map.length; i += 1) {
|
|
121
|
+
const entry = map[i];
|
|
122
|
+
copy[i] = entry === void 0 ? { startBeat: 0, ts: { numerator: 4, denominator: 4 } } : { startBeat: entry.startBeat, ts: copyTimeSignature(entry.ts) };
|
|
106
123
|
}
|
|
124
|
+
return copy;
|
|
125
|
+
}
|
|
126
|
+
function copyMeterData(data) {
|
|
127
|
+
return isMeterMap(data) ? copyMeterMap(data) : copyTimeSignature(data);
|
|
128
|
+
}
|
|
129
|
+
var INDEX_CACHE = /* @__PURE__ */ new WeakMap();
|
|
130
|
+
var readMap;
|
|
131
|
+
var readIndex;
|
|
132
|
+
function rememberRead(map, index) {
|
|
133
|
+
readMap = map;
|
|
134
|
+
readIndex = index;
|
|
107
135
|
return index;
|
|
108
136
|
}
|
|
109
|
-
function
|
|
137
|
+
function sameSignature(ts, snapshot) {
|
|
138
|
+
if (typeof ts !== "object" || ts === null) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (ts.numerator !== snapshot.numerator || ts.denominator !== snapshot.denominator) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const grouping = ts.grouping;
|
|
145
|
+
const was = snapshot.grouping;
|
|
146
|
+
if (grouping === void 0 || was === void 0) {
|
|
147
|
+
return grouping === was;
|
|
148
|
+
}
|
|
149
|
+
if (grouping.length !== was.length) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
for (let i = 0; i < was.length; i += 1) {
|
|
153
|
+
if (grouping[i] !== was[i]) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
function describesMap(index, map) {
|
|
160
|
+
if (index.startBeats.length !== map.length) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
for (let i = 0; i < map.length; i += 1) {
|
|
164
|
+
const entry = map[i];
|
|
165
|
+
const signature = index.signatures[i];
|
|
166
|
+
if (entry === void 0 || entry === null || signature === void 0 || entry.startBeat !== index.startBeats[i] || !sameSignature(entry.ts, signature)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
function buildMeterIndex(map, validated) {
|
|
173
|
+
const startBeats = new Array(map.length);
|
|
174
|
+
const signatures = new Array(map.length);
|
|
175
|
+
const barAnchors = new Array(map.length);
|
|
176
|
+
const barBeats = new Array(map.length);
|
|
177
|
+
const barsBefore = new Array(map.length);
|
|
110
178
|
let bars = 0;
|
|
111
|
-
for (let i = 0; i <
|
|
179
|
+
for (let i = 0; i < map.length; i += 1) {
|
|
112
180
|
const entry = map[i];
|
|
181
|
+
const startBeat = _nullishCoalesce(_optionalChain([entry, 'optionalAccess', _ => _.startBeat]), () => ( 0));
|
|
182
|
+
const barLen = entry === void 0 ? 0 : barBeatsOf(entry.ts);
|
|
183
|
+
const anchor = i === 0 ? 0 : startBeat;
|
|
184
|
+
startBeats[i] = startBeat;
|
|
185
|
+
signatures[i] = entry === void 0 ? { numerator: 4, denominator: 4 } : copyTimeSignature(entry.ts);
|
|
186
|
+
barAnchors[i] = anchor;
|
|
187
|
+
barBeats[i] = barLen;
|
|
188
|
+
barsBefore[i] = bars;
|
|
113
189
|
const next = map[i + 1];
|
|
114
|
-
if (
|
|
115
|
-
|
|
190
|
+
if (next !== void 0 && barLen > 0) {
|
|
191
|
+
bars += Math.max(1, Math.ceil((next.startBeat - anchor) / barLen - BEAT_EPS));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const index = { startBeats, signatures, barAnchors, barBeats, barsBefore, validated };
|
|
195
|
+
INDEX_CACHE.set(map, index);
|
|
196
|
+
return rememberRead(map, index);
|
|
197
|
+
}
|
|
198
|
+
function meterIndexOf(map) {
|
|
199
|
+
if (readMap === map && readIndex !== void 0) {
|
|
200
|
+
return readIndex;
|
|
201
|
+
}
|
|
202
|
+
const cached = INDEX_CACHE.get(map);
|
|
203
|
+
return cached !== void 0 && describesMap(cached, map) ? rememberRead(map, cached) : buildMeterIndex(map, false);
|
|
204
|
+
}
|
|
205
|
+
function isValidatedMeterMap(map) {
|
|
206
|
+
const cached = INDEX_CACHE.get(map);
|
|
207
|
+
if (_optionalChain([cached, 'optionalAccess', _2 => _2.validated]) !== true || !describesMap(cached, map)) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
rememberRead(map, cached);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
function rememberValidatedMeterMap(map) {
|
|
214
|
+
buildMeterIndex(map, true);
|
|
215
|
+
}
|
|
216
|
+
function lastAtOrBefore(keys, value) {
|
|
217
|
+
let low = 1;
|
|
218
|
+
let high = keys.length - 1;
|
|
219
|
+
let index = 0;
|
|
220
|
+
while (low <= high) {
|
|
221
|
+
const mid = low + high >>> 1;
|
|
222
|
+
if ((_nullishCoalesce(keys[mid], () => ( Number.POSITIVE_INFINITY))) <= value) {
|
|
223
|
+
index = mid;
|
|
224
|
+
low = mid + 1;
|
|
225
|
+
} else {
|
|
226
|
+
high = mid - 1;
|
|
116
227
|
}
|
|
117
|
-
bars += Math.max(1, Math.ceil((next.startBeat - entry.startBeat) / barBeatsOf(entry.ts) - EPS));
|
|
118
228
|
}
|
|
119
|
-
return
|
|
229
|
+
return index;
|
|
230
|
+
}
|
|
231
|
+
function entryIndexOf(map, beat) {
|
|
232
|
+
return lastAtOrBefore(meterIndexOf(map).startBeats, beat + BEAT_EPS);
|
|
120
233
|
}
|
|
121
234
|
function barStartOf(map, beat) {
|
|
122
|
-
const
|
|
123
|
-
|
|
235
|
+
const index = meterIndexOf(map);
|
|
236
|
+
const at = lastAtOrBefore(index.startBeats, beat + BEAT_EPS);
|
|
237
|
+
const anchor = index.barAnchors[at];
|
|
238
|
+
const barLen = index.barBeats[at];
|
|
239
|
+
if (anchor === void 0 || barLen === void 0 || barLen === 0) {
|
|
124
240
|
return 0;
|
|
125
241
|
}
|
|
126
|
-
|
|
127
|
-
return entry.startBeat + Math.floor((beat - entry.startBeat) / barLen + EPS) * barLen;
|
|
242
|
+
return anchor + Math.floor((beat - anchor) / barLen + BEAT_EPS) * barLen;
|
|
128
243
|
}
|
|
129
244
|
function barLengthOf(map, beat) {
|
|
130
|
-
const index =
|
|
131
|
-
const
|
|
132
|
-
|
|
245
|
+
const index = meterIndexOf(map);
|
|
246
|
+
const at = lastAtOrBefore(index.startBeats, beat + BEAT_EPS);
|
|
247
|
+
const full = index.barBeats[at];
|
|
248
|
+
if (full === void 0) {
|
|
133
249
|
return 0;
|
|
134
250
|
}
|
|
135
|
-
const
|
|
136
|
-
const next = _optionalChain([map, 'access', _3 => _3[index + 1], 'optionalAccess', _4 => _4.startBeat]);
|
|
251
|
+
const next = index.startBeats[at + 1];
|
|
137
252
|
return next === void 0 ? full : Math.min(full, next - barStartOf(map, beat));
|
|
138
253
|
}
|
|
139
254
|
function barIndexOf(map, beat) {
|
|
140
|
-
const index =
|
|
141
|
-
const
|
|
142
|
-
|
|
255
|
+
const index = meterIndexOf(map);
|
|
256
|
+
const at = lastAtOrBefore(index.startBeats, beat + BEAT_EPS);
|
|
257
|
+
const anchor = index.barAnchors[at];
|
|
258
|
+
const barLen = index.barBeats[at];
|
|
259
|
+
if (anchor === void 0 || barLen === void 0 || barLen === 0) {
|
|
143
260
|
return 0;
|
|
144
261
|
}
|
|
145
|
-
|
|
146
|
-
return barsBeforeEntry(map, index) + Math.floor((beat - entry.startBeat) / barLen + EPS);
|
|
262
|
+
return (_nullishCoalesce(index.barsBefore[at], () => ( 0))) + Math.floor((beat - anchor) / barLen + BEAT_EPS);
|
|
147
263
|
}
|
|
148
264
|
function beatOfBarIndex(map, barIndex) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const barLen = barBeatsOf(entry.ts);
|
|
156
|
-
const next = map[i + 1];
|
|
157
|
-
if (next === void 0) {
|
|
158
|
-
return entry.startBeat + (barIndex - base) * barLen;
|
|
159
|
-
}
|
|
160
|
-
const bars = Math.max(1, Math.ceil((next.startBeat - entry.startBeat) / barLen - EPS));
|
|
161
|
-
if (barIndex < base + bars) {
|
|
162
|
-
return entry.startBeat + (barIndex - base) * barLen;
|
|
163
|
-
}
|
|
164
|
-
base += bars;
|
|
265
|
+
const index = meterIndexOf(map);
|
|
266
|
+
const at = lastAtOrBefore(index.barsBefore, barIndex);
|
|
267
|
+
const anchor = index.barAnchors[at];
|
|
268
|
+
const barLen = index.barBeats[at];
|
|
269
|
+
if (anchor === void 0 || barLen === void 0) {
|
|
270
|
+
return 0;
|
|
165
271
|
}
|
|
166
|
-
return 0;
|
|
272
|
+
return anchor + (barIndex - (_nullishCoalesce(index.barsBefore[at], () => ( 0)))) * barLen;
|
|
167
273
|
}
|
|
168
274
|
|
|
169
275
|
// src/core/validation/index.ts
|
|
170
276
|
var DEFAULT_GENERATION_BUDGET = 1e6;
|
|
277
|
+
var MAX_METER_CHANGES = 1e5;
|
|
278
|
+
var MAX_ECHOED_LENGTH = 64;
|
|
279
|
+
function describeRejected(value) {
|
|
280
|
+
if (value === null) {
|
|
281
|
+
return "null";
|
|
282
|
+
}
|
|
283
|
+
if (typeof value !== "string") {
|
|
284
|
+
return typeof value;
|
|
285
|
+
}
|
|
286
|
+
return value.length <= MAX_ECHOED_LENGTH ? JSON.stringify(value) : `${JSON.stringify(value.slice(0, MAX_ECHOED_LENGTH))} (truncated from ${value.length} characters)`;
|
|
287
|
+
}
|
|
171
288
|
function assertFiniteNumber(value, name) {
|
|
172
289
|
if (!Number.isFinite(value)) {
|
|
173
290
|
throw new InvalidInputError(`${name} must be finite; received ${value}`);
|
|
174
291
|
}
|
|
175
292
|
return value;
|
|
176
293
|
}
|
|
294
|
+
function assertBounds(min, max, name) {
|
|
295
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
296
|
+
throw new InvalidInputError(
|
|
297
|
+
`${name} must be bounded by finite numbers; received [${describeRejected(min)}, ${describeRejected(max)}]`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
177
301
|
function assertInteger(value, name, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
|
|
178
302
|
assertFiniteNumber(value, name);
|
|
303
|
+
assertBounds(min, max, name);
|
|
179
304
|
if (!Number.isSafeInteger(value) || value < min || value > max) {
|
|
180
305
|
throw new InvalidInputError(
|
|
181
306
|
`${name} must be an integer in [${min}, ${max}]; received ${value}`
|
|
@@ -186,6 +311,9 @@ function assertInteger(value, name, min = Number.MIN_SAFE_INTEGER, max = Number.
|
|
|
186
311
|
function assertMidiPitch(value, name = "MIDI pitch") {
|
|
187
312
|
return assertInteger(value, name, 0, 127);
|
|
188
313
|
}
|
|
314
|
+
function assertPitchClass(value, name = "pitch class") {
|
|
315
|
+
return assertInteger(value, name, 0, 11);
|
|
316
|
+
}
|
|
189
317
|
function clampToMidi(value, name = "MIDI pitch") {
|
|
190
318
|
assertInteger(value, name);
|
|
191
319
|
return Math.min(127, Math.max(0, value));
|
|
@@ -197,13 +325,43 @@ function assertFiniteSemitones(value, name = "semitones") {
|
|
|
197
325
|
return assertFiniteNumber(value, name);
|
|
198
326
|
}
|
|
199
327
|
function dropSilentNotes(events) {
|
|
200
|
-
return events.filter(
|
|
328
|
+
return assertArray(events, "events").filter(
|
|
329
|
+
(event) => event !== void 0 && event !== null && event.durationBeat > 0
|
|
330
|
+
);
|
|
201
331
|
}
|
|
202
332
|
var soundingNotesOnly = dropSilentNotes;
|
|
333
|
+
function assertArray(value, name) {
|
|
334
|
+
if (!Array.isArray(value)) {
|
|
335
|
+
throw new InvalidInputError(`${name} must be an array; received ${describeRejected(value)}`);
|
|
336
|
+
}
|
|
337
|
+
return value;
|
|
338
|
+
}
|
|
339
|
+
function assertRecord(value, name) {
|
|
340
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
341
|
+
throw new InvalidInputError(`${name} must be an object; received ${describeRejected(value)}`);
|
|
342
|
+
}
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
function assertOptions(value, name = "options") {
|
|
346
|
+
return value === void 0 ? {} : assertRecord(value, name);
|
|
347
|
+
}
|
|
348
|
+
function assertFlag(value, name) {
|
|
349
|
+
if (typeof value !== "boolean") {
|
|
350
|
+
throw new InvalidInputError(`${name} must be a boolean; received ${describeRejected(value)}`);
|
|
351
|
+
}
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
354
|
+
function assertFunction(value, name) {
|
|
355
|
+
if (typeof value !== "function") {
|
|
356
|
+
throw new InvalidInputError(`${name} must be a function; received ${describeRejected(value)}`);
|
|
357
|
+
}
|
|
358
|
+
return value;
|
|
359
|
+
}
|
|
203
360
|
function assertOneOf(value, allowed, name) {
|
|
204
|
-
|
|
361
|
+
const names = assertArray(allowed, `${name} allowed values`);
|
|
362
|
+
if (typeof value !== "string" || !names.includes(value)) {
|
|
205
363
|
throw new InvalidInputError(
|
|
206
|
-
`${name} must be one of ${
|
|
364
|
+
`${name} must be one of ${names.join(", ")}; received ${describeRejected(value)}`
|
|
207
365
|
);
|
|
208
366
|
}
|
|
209
367
|
return value;
|
|
@@ -213,13 +371,14 @@ function assertPositiveInt(value, name, max = DEFAULT_GENERATION_BUDGET) {
|
|
|
213
371
|
}
|
|
214
372
|
function assertRange(value, min, max, name) {
|
|
215
373
|
assertFiniteNumber(value, name);
|
|
374
|
+
assertBounds(min, max, name);
|
|
216
375
|
if (value < min || value > max) {
|
|
217
376
|
throw new InvalidInputError(`${name} must be in [${min}, ${max}]; received ${value}`);
|
|
218
377
|
}
|
|
219
378
|
return value;
|
|
220
379
|
}
|
|
221
380
|
function assertGenerationBudget(estimated, name, limit = DEFAULT_GENERATION_BUDGET) {
|
|
222
|
-
const cap =
|
|
381
|
+
const cap = limit === void 0 ? DEFAULT_GENERATION_BUDGET : limit;
|
|
223
382
|
assertFiniteNumber(estimated, name);
|
|
224
383
|
assertPositiveInt(cap, `${name} limit`, Number.MAX_SAFE_INTEGER);
|
|
225
384
|
if (estimated < 0) {
|
|
@@ -232,10 +391,31 @@ function assertGenerationBudget(estimated, name, limit = DEFAULT_GENERATION_BUDG
|
|
|
232
391
|
}
|
|
233
392
|
return estimated;
|
|
234
393
|
}
|
|
394
|
+
function assertTableSize(rows, columns, name, budget) {
|
|
395
|
+
assertFiniteNumber(rows, `${name} rows`);
|
|
396
|
+
assertFiniteNumber(columns, `${name} columns`);
|
|
397
|
+
return assertGenerationBudget(rows * columns, name, budget);
|
|
398
|
+
}
|
|
399
|
+
function allocateCandidateTable(rows, columns, name, budget) {
|
|
400
|
+
return new Float64Array(assertTableSize(rows, columns, name, budget));
|
|
401
|
+
}
|
|
402
|
+
function allocateChoiceTable(rows, columns, name, budget) {
|
|
403
|
+
return new Int32Array(assertTableSize(rows, columns, name, budget));
|
|
404
|
+
}
|
|
235
405
|
function assertTimeSignature(ts, name = "time signature") {
|
|
406
|
+
if (typeof ts !== "object" || ts === null) {
|
|
407
|
+
throw new InvalidInputError(
|
|
408
|
+
`${name} must be a time signature; received ${describeRejected(ts)}`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
236
411
|
assertPositiveInt(ts.numerator, `${name}.numerator`);
|
|
237
412
|
assertPositiveInt(ts.denominator, `${name}.denominator`);
|
|
238
413
|
if (ts.grouping !== void 0) {
|
|
414
|
+
if (!Array.isArray(ts.grouping)) {
|
|
415
|
+
throw new InvalidInputError(
|
|
416
|
+
`${name}.grouping must be an array of group lengths; received ${describeRejected(ts.grouping)}`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
239
419
|
if (ts.grouping.length === 0) {
|
|
240
420
|
throw new InvalidInputError(`${name}.grouping must not be empty`);
|
|
241
421
|
}
|
|
@@ -258,11 +438,17 @@ function assertMeterMap(meters, name = "meters") {
|
|
|
258
438
|
if (meters.length === 0) {
|
|
259
439
|
throw new InvalidInputError(`${name} must not be empty`);
|
|
260
440
|
}
|
|
441
|
+
assertGenerationBudget(meters.length, `${name} count`, MAX_METER_CHANGES);
|
|
442
|
+
if (isValidatedMeterMap(meters)) {
|
|
443
|
+
return meters;
|
|
444
|
+
}
|
|
261
445
|
let previous = Number.NEGATIVE_INFINITY;
|
|
262
446
|
for (let index = 0; index < meters.length; index += 1) {
|
|
263
447
|
const entry = meters[index];
|
|
264
|
-
if (entry ===
|
|
265
|
-
throw new InvalidInputError(
|
|
448
|
+
if (typeof entry !== "object" || entry === null) {
|
|
449
|
+
throw new InvalidInputError(
|
|
450
|
+
`${name}[${index}] must be a meter change; received ${describeRejected(entry)}`
|
|
451
|
+
);
|
|
266
452
|
}
|
|
267
453
|
assertFiniteNumber(entry.startBeat, `${name}[${index}].startBeat`);
|
|
268
454
|
if (index > 0 && entry.startBeat <= previous) {
|
|
@@ -273,15 +459,22 @@ function assertMeterMap(meters, name = "meters") {
|
|
|
273
459
|
previous = entry.startBeat;
|
|
274
460
|
assertTimeSignature(entry.ts, `${name}[${index}].ts`);
|
|
275
461
|
}
|
|
462
|
+
rememberValidatedMeterMap(meters);
|
|
276
463
|
return meters;
|
|
277
464
|
}
|
|
278
465
|
function assertNoteEvent(event, name = "note event", options = {}) {
|
|
466
|
+
const asked = assertOptions(options, `${name} options`);
|
|
467
|
+
if (typeof event !== "object" || event === null) {
|
|
468
|
+
throw new InvalidInputError(
|
|
469
|
+
`${name} must be a note event; received ${describeRejected(event)}`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
279
472
|
assertMidiPitch(event.pitch, `${name}.pitch`);
|
|
280
|
-
const minStartBeat = _nullishCoalesce(
|
|
473
|
+
const minStartBeat = _nullishCoalesce(asked.minStartBeat, () => ( -Number.MAX_SAFE_INTEGER));
|
|
281
474
|
assertFiniteNumber(minStartBeat, "minStartBeat");
|
|
282
475
|
assertRange(event.startBeat, minStartBeat, Number.MAX_SAFE_INTEGER, `${name}.startBeat`);
|
|
283
476
|
assertFiniteNumber(event.durationBeat, `${name}.durationBeat`);
|
|
284
|
-
if (!
|
|
477
|
+
if (!asked.allowNonPositiveDuration && event.durationBeat <= 0) {
|
|
285
478
|
throw new InvalidInputError(
|
|
286
479
|
`${name}.durationBeat must be positive; received ${event.durationBeat}`
|
|
287
480
|
);
|
|
@@ -293,21 +486,27 @@ function assertNoteEvent(event, name = "note event", options = {}) {
|
|
|
293
486
|
`${name}.durationBeat`
|
|
294
487
|
);
|
|
295
488
|
if (event.velocity !== void 0) {
|
|
296
|
-
|
|
489
|
+
assertInteger(event.velocity, `${name}.velocity`, 0, 127);
|
|
490
|
+
}
|
|
491
|
+
if (event.articulation !== void 0) {
|
|
492
|
+
assertOneOf(event.articulation, ARTICULATIONS, `${name}.articulation`);
|
|
297
493
|
}
|
|
298
494
|
return event;
|
|
299
495
|
}
|
|
300
496
|
function assertNoteEvents(events, name = "note events", options = {}) {
|
|
497
|
+
const asked = assertOptions(options, `${name} options`);
|
|
301
498
|
if (!Array.isArray(events)) {
|
|
302
499
|
throw new InvalidInputError(`${name} must be an array; received ${typeof events}`);
|
|
303
500
|
}
|
|
304
|
-
assertGenerationBudget(events.length, `${name} count`,
|
|
501
|
+
assertGenerationBudget(events.length, `${name} count`, asked.budget);
|
|
305
502
|
for (let index = 0; index < events.length; index += 1) {
|
|
306
503
|
const event = events[index];
|
|
307
|
-
if (event ===
|
|
308
|
-
throw new InvalidInputError(
|
|
504
|
+
if (typeof event !== "object" || event === null) {
|
|
505
|
+
throw new InvalidInputError(
|
|
506
|
+
`${name}[${index}] must be a note event; received ${describeRejected(event)}`
|
|
507
|
+
);
|
|
309
508
|
}
|
|
310
|
-
assertNoteEvent(event, `${name}[${index}]`,
|
|
509
|
+
assertNoteEvent(event, `${name}[${index}]`, asked);
|
|
311
510
|
}
|
|
312
511
|
return events;
|
|
313
512
|
}
|
|
@@ -343,6 +542,30 @@ var JAPANESE_DOUBLE = "\u91CD";
|
|
|
343
542
|
var ITALIAN_SHARP = "diesis";
|
|
344
543
|
var ITALIAN_FLAT = "bemolle";
|
|
345
544
|
var ITALIAN_DOUBLE = "doppio";
|
|
545
|
+
var MAX_NAME_ACCIDENTALS = 2;
|
|
546
|
+
function longestGlyph(glyphs) {
|
|
547
|
+
return Math.max(...glyphs.map((glyph) => glyph.length));
|
|
548
|
+
}
|
|
549
|
+
var MAX_LETTER_LENGTH = {
|
|
550
|
+
english: longestGlyph(ENGLISH_LETTERS),
|
|
551
|
+
german: longestGlyph(GERMAN_LETTERS),
|
|
552
|
+
japanese: longestGlyph(JAPANESE_LETTERS),
|
|
553
|
+
italian: longestGlyph(ITALIAN_LETTERS)
|
|
554
|
+
};
|
|
555
|
+
var MAX_ACCIDENTAL_LENGTH = {
|
|
556
|
+
/** `#`, `x` and `b`. */
|
|
557
|
+
english: 1,
|
|
558
|
+
/** The `-is` and `-es` affixes, whose first mark may elide to a bare `s`. */
|
|
559
|
+
german: 2,
|
|
560
|
+
/** 嬰 and 変; 重 stands in for the second mark of a double, not beside it. */
|
|
561
|
+
japanese: 1,
|
|
562
|
+
/** A space and the accidental word; `doppio` covers two marks in one word. */
|
|
563
|
+
italian: 1 + Math.max(ITALIAN_SHARP.length, ITALIAN_FLAT.length)
|
|
564
|
+
};
|
|
565
|
+
function maxNoteNameLength(system) {
|
|
566
|
+
const table = tableOf(assertSystem(system));
|
|
567
|
+
return MAX_LETTER_LENGTH[table] + MAX_NAME_ACCIDENTALS * MAX_ACCIDENTAL_LENGTH[table];
|
|
568
|
+
}
|
|
346
569
|
function normalize(text) {
|
|
347
570
|
return text.trim().replace(/\s+/g, " ");
|
|
348
571
|
}
|
|
@@ -498,12 +721,19 @@ function formatItalian(note) {
|
|
|
498
721
|
const accidental = marks === 2 ? `${ITALIAN_DOUBLE} ${word}` : Array.from({ length: marks }, () => word).join(" ");
|
|
499
722
|
return `${glyph} ${accidental}`;
|
|
500
723
|
}
|
|
724
|
+
function isDigit(ch) {
|
|
725
|
+
return ch >= "0" && ch <= "9";
|
|
726
|
+
}
|
|
501
727
|
function splitOctave(text) {
|
|
502
|
-
|
|
503
|
-
|
|
728
|
+
let start = text.length;
|
|
729
|
+
while (start > 0 && isDigit(text.charAt(start - 1))) {
|
|
730
|
+
start -= 1;
|
|
731
|
+
}
|
|
732
|
+
if (start === text.length) {
|
|
504
733
|
return { name: text, octave: void 0 };
|
|
505
734
|
}
|
|
506
|
-
|
|
735
|
+
const signed = start > 0 && text.charAt(start - 1) === "-" ? start - 1 : start;
|
|
736
|
+
return { name: text.slice(0, signed), octave: Number.parseInt(text.slice(signed), 10) };
|
|
507
737
|
}
|
|
508
738
|
function parseIn(text, system) {
|
|
509
739
|
const table = tableOf(system);
|
|
@@ -518,6 +748,13 @@ function parseIn(text, system) {
|
|
|
518
748
|
}
|
|
519
749
|
return octave === void 0 ? note : { ...note, octave };
|
|
520
750
|
}
|
|
751
|
+
function trimSeparators(text) {
|
|
752
|
+
let end = text.length;
|
|
753
|
+
while (end > 0 && /[\s-]/.test(text.charAt(end - 1))) {
|
|
754
|
+
end -= 1;
|
|
755
|
+
}
|
|
756
|
+
return text.slice(0, end);
|
|
757
|
+
}
|
|
521
758
|
function splitModeWord(text) {
|
|
522
759
|
const lower = text.toLowerCase();
|
|
523
760
|
let best;
|
|
@@ -533,27 +770,31 @@ function splitModeWord(text) {
|
|
|
533
770
|
return { tonic: text, mode: void 0, system: void 0 };
|
|
534
771
|
}
|
|
535
772
|
return {
|
|
536
|
-
tonic: text.slice(0, text.length - best.word.length)
|
|
773
|
+
tonic: trimSeparators(text.slice(0, text.length - best.word.length)),
|
|
537
774
|
mode: best.mode,
|
|
538
775
|
system: best.system
|
|
539
776
|
};
|
|
540
777
|
}
|
|
541
778
|
function resolveSystem(text) {
|
|
542
779
|
const { tonic, system: wordSystem } = splitModeWord(text);
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
780
|
+
if (wordSystem === void 0) {
|
|
781
|
+
for (const system of DETECTION_ORDER) {
|
|
782
|
+
if (parseIn(tonic, system) !== null) {
|
|
783
|
+
return system;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
546
786
|
return null;
|
|
547
787
|
}
|
|
548
|
-
if (wordSystem
|
|
549
|
-
return
|
|
788
|
+
if (parseIn(tonic, wordSystem) !== null) {
|
|
789
|
+
return wordSystem;
|
|
550
790
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
);
|
|
791
|
+
const accepted = DETECTION_ORDER.filter((system) => parseIn(tonic, system) !== null);
|
|
792
|
+
if (accepted.length === 0) {
|
|
793
|
+
return null;
|
|
555
794
|
}
|
|
556
|
-
|
|
795
|
+
throw new InvalidInputError(
|
|
796
|
+
`${describeRejected(text)} mixes note-name systems: ${describeRejected(tonic)} is ${accepted.join(" or ")} but its mode word is ${wordSystem}`
|
|
797
|
+
);
|
|
557
798
|
}
|
|
558
799
|
function detectNoteNameSystem(text) {
|
|
559
800
|
if (typeof text !== "string") {
|
|
@@ -561,21 +802,23 @@ function detectNoteNameSystem(text) {
|
|
|
561
802
|
}
|
|
562
803
|
const system = resolveSystem(normalize(text));
|
|
563
804
|
if (system === null) {
|
|
564
|
-
throw new InvalidInputError(`no note-name system reads ${
|
|
805
|
+
throw new InvalidInputError(`no note-name system reads ${describeRejected(text)}`);
|
|
565
806
|
}
|
|
566
807
|
return system;
|
|
567
808
|
}
|
|
568
809
|
function readNoteName(text, opts) {
|
|
810
|
+
const asked = assertOptions(opts, "opts");
|
|
569
811
|
const name = normalize(text);
|
|
570
|
-
const system =
|
|
812
|
+
const system = asked.system === void 0 ? resolveSystem(name) : assertSystem(asked.system);
|
|
571
813
|
const note = system === null ? null : parseIn(name, system);
|
|
572
814
|
if (note === null) {
|
|
573
|
-
throw new InvalidInputError(`Invalid note: ${text}`);
|
|
815
|
+
throw new InvalidInputError(`Invalid note: ${describeRejected(text)}`);
|
|
574
816
|
}
|
|
575
817
|
return note;
|
|
576
818
|
}
|
|
577
819
|
function writeNoteName(note, opts) {
|
|
578
|
-
const
|
|
820
|
+
const asked = assertOptions(opts, "opts");
|
|
821
|
+
const system = asked.system === void 0 ? "english" : assertSystem(asked.system);
|
|
579
822
|
const table = tableOf(system);
|
|
580
823
|
const name = table === "german" ? formatGerman(note) : table === "japanese" ? formatJapanese(note) : table === "italian" ? formatItalian(note) : formatEnglish(note);
|
|
581
824
|
return note.octave === void 0 ? name : `${name}${note.octave}`;
|
|
@@ -585,24 +828,26 @@ function impliedMode(system, tonic) {
|
|
|
585
828
|
}
|
|
586
829
|
function readKeyName(text, opts) {
|
|
587
830
|
const name = normalize(text);
|
|
831
|
+
const asked = assertOptions(opts, "opts");
|
|
588
832
|
const split = splitModeWord(name);
|
|
589
|
-
const system =
|
|
833
|
+
const system = asked.system === void 0 ? resolveSystem(name) : assertSystem(asked.system);
|
|
590
834
|
if (system !== null && split.system !== void 0 && split.system !== tableOf(system)) {
|
|
591
835
|
throw new InvalidInputError(
|
|
592
|
-
`${
|
|
836
|
+
`${describeRejected(text)} is not a ${system} key name: its mode word is ${split.system}`
|
|
593
837
|
);
|
|
594
838
|
}
|
|
595
839
|
const tonic = system === null ? null : parseIn(split.tonic, system);
|
|
596
840
|
if (tonic === null || system === null) {
|
|
597
|
-
throw new InvalidInputError(`Invalid key name: ${text}`);
|
|
841
|
+
throw new InvalidInputError(`Invalid key name: ${describeRejected(text)}`);
|
|
598
842
|
}
|
|
599
843
|
if (tonic.octave !== void 0) {
|
|
600
|
-
throw new InvalidInputError(`a key name carries no octave; received ${
|
|
844
|
+
throw new InvalidInputError(`a key name carries no octave; received ${describeRejected(text)}`);
|
|
601
845
|
}
|
|
602
846
|
return { tonic, mode: _nullishCoalesce(split.mode, () => ( impliedMode(system, split.tonic))) };
|
|
603
847
|
}
|
|
604
848
|
function writeKeyName(key, opts) {
|
|
605
|
-
const
|
|
849
|
+
const asked = assertOptions(opts, "opts");
|
|
850
|
+
const system = asked.system === void 0 ? "english" : assertSystem(asked.system);
|
|
606
851
|
const table = tableOf(system);
|
|
607
852
|
const word = MODE_WORDS[table][key.mode];
|
|
608
853
|
const tonic = writeNoteName(key.tonic, { system });
|
|
@@ -619,11 +864,13 @@ function writeKeyName(key, opts) {
|
|
|
619
864
|
// src/core/pitch/index.ts
|
|
620
865
|
var LETTER_SEMITONES = [0, 2, 4, 5, 7, 9, 11];
|
|
621
866
|
var MAX_ALTER = 6;
|
|
867
|
+
var MAX_OCTAVE = 100;
|
|
622
868
|
function assertNote(note, name) {
|
|
869
|
+
assertRecord(note, name);
|
|
623
870
|
assertInteger(note.letter, `${name}.letter`, 0, 6);
|
|
624
871
|
assertInteger(note.alter, `${name}.alter`, -MAX_ALTER, MAX_ALTER);
|
|
625
872
|
if (note.octave !== void 0) {
|
|
626
|
-
assertInteger(note.octave, `${name}.octave`, -
|
|
873
|
+
assertInteger(note.octave, `${name}.octave`, -MAX_OCTAVE, MAX_OCTAVE);
|
|
627
874
|
}
|
|
628
875
|
return note;
|
|
629
876
|
}
|
|
@@ -644,11 +891,12 @@ function parseNote(text, opts) {
|
|
|
644
891
|
return unwrapParse(tryParseNote(text, opts));
|
|
645
892
|
}
|
|
646
893
|
function tryParseNote(text, opts) {
|
|
894
|
+
const asked = assertOptions(opts, "opts");
|
|
647
895
|
try {
|
|
648
896
|
if (typeof text !== "string") {
|
|
649
897
|
throw new InvalidInputError(`note must be a string; received ${typeof text}`);
|
|
650
898
|
}
|
|
651
|
-
return { ok: true, value: assertNote(readNoteName(text,
|
|
899
|
+
return { ok: true, value: assertNote(readNoteName(text, asked), `note ${text}`) };
|
|
652
900
|
} catch (error) {
|
|
653
901
|
return parseFailure(error);
|
|
654
902
|
}
|
|
@@ -661,11 +909,12 @@ function parseKeyName(text, opts) {
|
|
|
661
909
|
return unwrapParse(tryParseKeyName(text, opts));
|
|
662
910
|
}
|
|
663
911
|
function tryParseKeyName(text, opts) {
|
|
912
|
+
const asked = assertOptions(opts, "opts");
|
|
664
913
|
try {
|
|
665
914
|
if (typeof text !== "string") {
|
|
666
915
|
throw new InvalidInputError(`key name must be a string; received ${typeof text}`);
|
|
667
916
|
}
|
|
668
|
-
const key = readKeyName(text,
|
|
917
|
+
const key = readKeyName(text, asked);
|
|
669
918
|
assertNote(key.tonic, `key ${text}`);
|
|
670
919
|
return { ok: true, value: key };
|
|
671
920
|
} catch (error) {
|
|
@@ -673,10 +922,11 @@ function tryParseKeyName(text, opts) {
|
|
|
673
922
|
}
|
|
674
923
|
}
|
|
675
924
|
function formatKeyName(key, opts) {
|
|
676
|
-
|
|
677
|
-
|
|
925
|
+
const named = assertRecord(key, "key");
|
|
926
|
+
assertNote(named.tonic, "key.tonic");
|
|
927
|
+
const mode = assertOneOf(named.mode, ["major", "minor"], "key.mode");
|
|
678
928
|
return writeKeyName(
|
|
679
|
-
{ tonic: { letter: mod7(
|
|
929
|
+
{ tonic: { letter: mod7(named.tonic.letter), alter: named.tonic.alter }, mode },
|
|
680
930
|
opts
|
|
681
931
|
);
|
|
682
932
|
}
|
|
@@ -735,12 +985,14 @@ function midiToNote(midi, spelling = "sharp") {
|
|
|
735
985
|
}
|
|
736
986
|
var LETTER_STEPS_BY_SEMITONE = [0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6];
|
|
737
987
|
function transposeNote(note, semitones, opts) {
|
|
988
|
+
assertNote(note, "note");
|
|
989
|
+
const asked = assertOptions(opts, "opts");
|
|
738
990
|
if (!Number.isFinite(semitones)) {
|
|
739
991
|
throw new InvalidInputError(`semitones must be a finite number; received ${semitones}`);
|
|
740
992
|
}
|
|
741
993
|
const steps = Math.round(semitones);
|
|
742
|
-
if (
|
|
743
|
-
return note.octave === void 0 ? bareOf(midiToNote(60 + mod12(noteToPitchClass(note) + steps),
|
|
994
|
+
if (asked.spelling !== void 0) {
|
|
995
|
+
return note.octave === void 0 ? bareOf(midiToNote(60 + mod12(noteToPitchClass(note) + steps), asked.spelling)) : midiToNote(noteToMidi(note) + steps, asked.spelling);
|
|
744
996
|
}
|
|
745
997
|
const direction = steps < 0 ? -1 : 1;
|
|
746
998
|
const magnitude = Math.abs(steps);
|
|
@@ -751,10 +1003,16 @@ function transposeNote(note, semitones, opts) {
|
|
|
751
1003
|
const letter = mod7(absoluteLetter);
|
|
752
1004
|
const natural = _nullishCoalesce(LETTER_SEMITONES[letter], () => ( 0));
|
|
753
1005
|
if (note.octave === void 0) {
|
|
754
|
-
return
|
|
1006
|
+
return assertNote(
|
|
1007
|
+
{ letter, alter: alterFor(natural, mod12(noteToPitchClass(note) + steps)) },
|
|
1008
|
+
"transposed note"
|
|
1009
|
+
);
|
|
755
1010
|
}
|
|
756
1011
|
const octave = note.octave + Math.floor(absoluteLetter / 7);
|
|
757
|
-
return
|
|
1012
|
+
return assertNote(
|
|
1013
|
+
{ letter, alter: noteToMidi(note) + steps - ((octave + 1) * 12 + natural), octave },
|
|
1014
|
+
"transposed note"
|
|
1015
|
+
);
|
|
758
1016
|
}
|
|
759
1017
|
function bareOf(note) {
|
|
760
1018
|
return { letter: note.letter, alter: note.alter };
|
|
@@ -787,7 +1045,7 @@ function qualityFromSpan(numberValue, semitones) {
|
|
|
787
1045
|
}
|
|
788
1046
|
return delta > 0 ? repeat("A", delta) : repeat("d", -delta - 1);
|
|
789
1047
|
}
|
|
790
|
-
var MAX_INTERVAL_NUMBER =
|
|
1048
|
+
var MAX_INTERVAL_NUMBER = 7 * (2 * MAX_OCTAVE) + 6 + 1;
|
|
791
1049
|
function intervalSemitones(numberValue, quality) {
|
|
792
1050
|
assertInteger(numberValue, "interval number", 1, MAX_INTERVAL_NUMBER);
|
|
793
1051
|
const octaves = Math.floor((numberValue - 1) / 7);
|
|
@@ -819,9 +1077,9 @@ function intervalSemitones(numberValue, quality) {
|
|
|
819
1077
|
`a unison cannot be diminished; a unison narrowed by a semitone descends, so name it '-${"A".repeat(quality.length)}1'`
|
|
820
1078
|
);
|
|
821
1079
|
}
|
|
822
|
-
return
|
|
1080
|
+
return reference - quality.length - (perfect ? 0 : 1);
|
|
823
1081
|
}
|
|
824
|
-
throw new InvalidInputError(`unknown interval quality ${
|
|
1082
|
+
throw new InvalidInputError(`unknown interval quality ${describeRejected(quality)}`);
|
|
825
1083
|
}
|
|
826
1084
|
var INTERVAL_NAME_PATTERN = /^(-?)(P|M|m|A+|d+)(\d+)$/;
|
|
827
1085
|
function parseInterval(name) {
|
|
@@ -830,16 +1088,19 @@ function parseInterval(name) {
|
|
|
830
1088
|
function tryParseInterval(name) {
|
|
831
1089
|
try {
|
|
832
1090
|
const match = typeof name === "string" ? name.trim().match(INTERVAL_NAME_PATTERN) : null;
|
|
833
|
-
const quality = _optionalChain([match, 'optionalAccess',
|
|
834
|
-
const numberValue = Number(_optionalChain([match, 'optionalAccess',
|
|
1091
|
+
const quality = _optionalChain([match, 'optionalAccess', _3 => _3[2]]);
|
|
1092
|
+
const numberValue = Number(_optionalChain([match, 'optionalAccess', _4 => _4[3]]));
|
|
835
1093
|
if (quality === void 0 || !Number.isFinite(numberValue)) {
|
|
836
1094
|
throw new InvalidInputError(
|
|
837
|
-
`interval must be a quality followed by a number, such as 'P5'; received ${
|
|
1095
|
+
`interval must be a quality followed by a number, such as 'P5'; received ${describeRejected(name)}`
|
|
838
1096
|
);
|
|
839
1097
|
}
|
|
840
1098
|
const span = intervalSemitones(numberValue, quality);
|
|
841
|
-
if (_optionalChain([match, 'optionalAccess',
|
|
842
|
-
return {
|
|
1099
|
+
if (_optionalChain([match, 'optionalAccess', _5 => _5[1]]) !== "-") {
|
|
1100
|
+
return {
|
|
1101
|
+
ok: true,
|
|
1102
|
+
value: { number: numberValue, quality, semitones: span, descending: false }
|
|
1103
|
+
};
|
|
843
1104
|
}
|
|
844
1105
|
return {
|
|
845
1106
|
ok: true,
|
|
@@ -854,29 +1115,26 @@ function isDescendingInterval(interval) {
|
|
|
854
1115
|
return interval.descending;
|
|
855
1116
|
}
|
|
856
1117
|
const span = Math.round(interval.semitones);
|
|
857
|
-
if (
|
|
858
|
-
return
|
|
1118
|
+
if (qualityFromSpan(interval.number, span) === interval.quality) {
|
|
1119
|
+
return interval.number === 1 && span < 0;
|
|
859
1120
|
}
|
|
860
|
-
return span
|
|
1121
|
+
return span !== 0;
|
|
861
1122
|
}
|
|
862
1123
|
function normalizedInterval(data) {
|
|
863
1124
|
assertInteger(data.number, "interval.number", 1);
|
|
864
1125
|
assertFiniteNumber(data.semitones, "interval.semitones");
|
|
865
1126
|
const expected = intervalSemitones(data.number, data.quality);
|
|
866
|
-
if (Math.abs(data.semitones) !== expected) {
|
|
1127
|
+
if (Math.abs(data.semitones) !== Math.abs(expected)) {
|
|
867
1128
|
throw new InvalidInputError(
|
|
868
1129
|
`${data.quality}${data.number} spans ${expected} semitones; received ${data.semitones}`
|
|
869
1130
|
);
|
|
870
1131
|
}
|
|
871
|
-
|
|
1132
|
+
return {
|
|
872
1133
|
number: data.number,
|
|
873
1134
|
quality: data.quality,
|
|
874
|
-
semitones: data.semitones
|
|
1135
|
+
semitones: data.semitones,
|
|
1136
|
+
descending: isDescendingInterval(data)
|
|
875
1137
|
};
|
|
876
|
-
if (isDescendingInterval(data)) {
|
|
877
|
-
normalized.descending = true;
|
|
878
|
-
}
|
|
879
|
-
return normalized;
|
|
880
1138
|
}
|
|
881
1139
|
function toSpelledInterval(value) {
|
|
882
1140
|
if (typeof value === "string") {
|
|
@@ -895,7 +1153,7 @@ function toNoteData(value) {
|
|
|
895
1153
|
return parseNote(value);
|
|
896
1154
|
}
|
|
897
1155
|
if (typeof value === "number") {
|
|
898
|
-
return midiToNote(value);
|
|
1156
|
+
return midiToNote(assertMidiPitch(value, "note"));
|
|
899
1157
|
}
|
|
900
1158
|
if (typeof value === "object" && value !== null) {
|
|
901
1159
|
const data = "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON() : value;
|
|
@@ -906,9 +1164,10 @@ function toNoteData(value) {
|
|
|
906
1164
|
`note must be a name, a MIDI number, or spelled note data; received ${typeof value}`
|
|
907
1165
|
);
|
|
908
1166
|
}
|
|
909
|
-
function transposeByInterval(
|
|
910
|
-
|
|
911
|
-
|
|
1167
|
+
function transposeByInterval(given, named) {
|
|
1168
|
+
const note = toNoteData(given);
|
|
1169
|
+
const interval = toSpelledInterval(named);
|
|
1170
|
+
assertInteger(interval.number, "interval.number", 1, MAX_INTERVAL_NUMBER);
|
|
912
1171
|
assertFiniteNumber(interval.semitones, "interval.semitones");
|
|
913
1172
|
const letterSteps = (interval.number - 1) * (isDescendingInterval(interval) ? -1 : 1);
|
|
914
1173
|
const absoluteLetter = mod7(note.letter) + letterSteps;
|
|
@@ -916,10 +1175,16 @@ function transposeByInterval(note, interval) {
|
|
|
916
1175
|
const natural = _nullishCoalesce(LETTER_SEMITONES[letter], () => ( 0));
|
|
917
1176
|
const steps = Math.round(interval.semitones);
|
|
918
1177
|
if (note.octave === void 0) {
|
|
919
|
-
return
|
|
1178
|
+
return assertNote(
|
|
1179
|
+
{ letter, alter: alterFor(natural, mod12(noteToPitchClass(note) + steps)) },
|
|
1180
|
+
"transposed note"
|
|
1181
|
+
);
|
|
920
1182
|
}
|
|
921
1183
|
const octave = note.octave + Math.floor(absoluteLetter / 7);
|
|
922
|
-
return
|
|
1184
|
+
return assertNote(
|
|
1185
|
+
{ letter, alter: noteToMidi(note) + steps - ((octave + 1) * 12 + natural), octave },
|
|
1186
|
+
"transposed note"
|
|
1187
|
+
);
|
|
923
1188
|
}
|
|
924
1189
|
function diatonicIndex(note) {
|
|
925
1190
|
return mod7(note.letter) + 7 * (_nullishCoalesce(note.octave, () => ( 0)));
|
|
@@ -952,15 +1217,12 @@ function spelledInterval(a, b) {
|
|
|
952
1217
|
const absSteps = Math.abs(letterSteps);
|
|
953
1218
|
const number = absSteps + 1;
|
|
954
1219
|
const directedSpan = letterSteps === 0 ? Math.abs(semitones) : letterSteps > 0 ? semitones : -semitones;
|
|
955
|
-
|
|
1220
|
+
return {
|
|
956
1221
|
number,
|
|
957
1222
|
quality: qualityFromSpan(number, directedSpan),
|
|
958
|
-
semitones
|
|
1223
|
+
semitones,
|
|
1224
|
+
descending: letterSteps < 0 || letterSteps === 0 && semitones < 0
|
|
959
1225
|
};
|
|
960
|
-
if (letterSteps < 0 || letterSteps === 0 && semitones < 0) {
|
|
961
|
-
interval.descending = true;
|
|
962
|
-
}
|
|
963
|
-
return interval;
|
|
964
1226
|
}
|
|
965
1227
|
|
|
966
1228
|
// src/core/interval/index.ts
|
|
@@ -970,10 +1232,13 @@ var ConsonanceClass = /* @__PURE__ */ ((ConsonanceClass2) => {
|
|
|
970
1232
|
ConsonanceClass2[ConsonanceClass2["Dissonance"] = 2] = "Dissonance";
|
|
971
1233
|
return ConsonanceClass2;
|
|
972
1234
|
})(ConsonanceClass || {});
|
|
1235
|
+
Object.freeze(ConsonanceClass);
|
|
973
1236
|
function simpleInterval(semitones) {
|
|
1237
|
+
assertFiniteSemitones(semitones);
|
|
974
1238
|
return pitchClassOf(Math.abs(semitones));
|
|
975
1239
|
}
|
|
976
1240
|
function classifyInterval(semitones, twoVoice = true) {
|
|
1241
|
+
assertFlag(twoVoice, "twoVoice");
|
|
977
1242
|
const pc = simpleInterval(semitones);
|
|
978
1243
|
if (pc === 0 || pc === 7) {
|
|
979
1244
|
return 0 /* PerfectConsonance */;
|
|
@@ -991,6 +1256,7 @@ function isPerfectInterval(semitones) {
|
|
|
991
1256
|
return pc === 0 || pc === 7;
|
|
992
1257
|
}
|
|
993
1258
|
function isConsonantInterval(semitones, twoVoice = true) {
|
|
1259
|
+
assertFlag(twoVoice, "twoVoice");
|
|
994
1260
|
const pc = simpleInterval(semitones);
|
|
995
1261
|
if (pc === 5) {
|
|
996
1262
|
return !twoVoice;
|
|
@@ -998,6 +1264,316 @@ function isConsonantInterval(semitones, twoVoice = true) {
|
|
|
998
1264
|
return pc === 0 || pc === 3 || pc === 4 || pc === 7 || pc === 8 || pc === 9;
|
|
999
1265
|
}
|
|
1000
1266
|
|
|
1267
|
+
// src/core/meter/index.ts
|
|
1268
|
+
function toMeterData(value, name = "meter") {
|
|
1269
|
+
return copyMeterData(readMeterData(value, name));
|
|
1270
|
+
}
|
|
1271
|
+
function readMeterData(value, name = "meter") {
|
|
1272
|
+
if (typeof value === "string") {
|
|
1273
|
+
const parsed = tryParseTimeSignature(value);
|
|
1274
|
+
if (parsed.ok) {
|
|
1275
|
+
return parsed.value;
|
|
1276
|
+
}
|
|
1277
|
+
throw new InvalidInputError(`${name} must name a time signature; ${parsed.error.message}`);
|
|
1278
|
+
}
|
|
1279
|
+
if (typeof value === "object" && value !== null) {
|
|
1280
|
+
const data = !Array.isArray(value) && "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON() : value;
|
|
1281
|
+
if (isMeterMap(data)) {
|
|
1282
|
+
return assertMeterMap(data, name);
|
|
1283
|
+
}
|
|
1284
|
+
if (typeof data === "object" && data !== null && "numerator" in data) {
|
|
1285
|
+
return assertTimeSignature(data, name);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
throw new InvalidInputError(
|
|
1289
|
+
`${name} must be a time signature, a meter map, or a signature name; received ${describeRejected(value)}`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
function readSignature(value, name) {
|
|
1293
|
+
const resolved = readMeterData(value, name);
|
|
1294
|
+
return isMeterMap(resolved) ? meterAtChecked(0, resolved) : resolved;
|
|
1295
|
+
}
|
|
1296
|
+
var DEFAULT_TS = Object.freeze({ numerator: 4, denominator: 4 });
|
|
1297
|
+
var MAX_CHANGE_DIVISIONS = 16;
|
|
1298
|
+
function isMultiple(value, unit) {
|
|
1299
|
+
if (unit === 0) {
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
const ratio = value / unit;
|
|
1303
|
+
return Math.abs(ratio - Math.round(ratio)) < BEAT_EPS;
|
|
1304
|
+
}
|
|
1305
|
+
function parseTimeSignature(text) {
|
|
1306
|
+
return unwrapParse(tryParseTimeSignature(text));
|
|
1307
|
+
}
|
|
1308
|
+
function tryParseTimeSignature(text) {
|
|
1309
|
+
try {
|
|
1310
|
+
if (typeof text !== "string") {
|
|
1311
|
+
throw new InvalidInputError(`time signature must be a string; received ${typeof text}`);
|
|
1312
|
+
}
|
|
1313
|
+
return { ok: true, value: readTimeSignature(text) };
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
return parseFailure(error);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
function readTimeSignature(text) {
|
|
1319
|
+
const match = /^\s*((?:\d+\s*\+\s*)*\d+)\s*\/\s*(\d+)\s*$/.exec(text);
|
|
1320
|
+
if (!match) {
|
|
1321
|
+
throw new InvalidInputError(`Invalid time signature: ${text}`);
|
|
1322
|
+
}
|
|
1323
|
+
const groupTokens = (_nullishCoalesce(match[1], () => ( ""))).split("+").map((token) => Number.parseInt(token.trim(), 10));
|
|
1324
|
+
const numerator = groupTokens.reduce((sum, value) => sum + value, 0);
|
|
1325
|
+
const denominator = Number.parseInt(_nullishCoalesce(match[2], () => ( "")), 10);
|
|
1326
|
+
if (!Number.isSafeInteger(numerator) || !Number.isSafeInteger(denominator) || numerator <= 0 || denominator <= 0) {
|
|
1327
|
+
throw new InvalidInputError(`Invalid time signature: ${text}`);
|
|
1328
|
+
}
|
|
1329
|
+
const ts = groupTokens.length > 1 ? { numerator, denominator, grouping: groupTokens } : { numerator, denominator };
|
|
1330
|
+
return assertTimeSignature(ts, `time signature ${text}`);
|
|
1331
|
+
}
|
|
1332
|
+
function formatTimeSignature(ts, opts = {}) {
|
|
1333
|
+
const asked = assertOptions(opts, "opts");
|
|
1334
|
+
const signature = readSignature(ts, "ts");
|
|
1335
|
+
const grouping = signature.grouping;
|
|
1336
|
+
if (asked.grouping === true && grouping !== void 0) {
|
|
1337
|
+
if (groupingSumOf(signature) === signature.numerator) {
|
|
1338
|
+
return `${grouping.join("+")}/${signature.denominator}`;
|
|
1339
|
+
}
|
|
1340
|
+
const pulseGrouping = pulseGroupingOf(signature);
|
|
1341
|
+
if (pulseGrouping !== void 0 && !isUniformGrouping(pulseGrouping)) {
|
|
1342
|
+
throw new InvalidInputError(
|
|
1343
|
+
`ts carries a grouping no signature text can spell: ${grouping.join("+")} counted in the pulses of ${signature.numerator}/${signature.denominator}. Format it without the grouping to name the bar it is written in.`
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return `${signature.numerator}/${signature.denominator}`;
|
|
1348
|
+
}
|
|
1349
|
+
function isCompound(ts) {
|
|
1350
|
+
const signature = readSignature(ts, "ts");
|
|
1351
|
+
return isCompoundNumerator(signature.numerator) && !isAdditiveReading(signature);
|
|
1352
|
+
}
|
|
1353
|
+
function beatsPerBar(ts) {
|
|
1354
|
+
return barBeatsOf(readSignature(ts, "ts"));
|
|
1355
|
+
}
|
|
1356
|
+
function meterAt(beatInQuarters, meter) {
|
|
1357
|
+
assertFiniteNumber(beatInQuarters, "beat");
|
|
1358
|
+
return copyTimeSignature(meterAtChecked(beatInQuarters, readMeterData(meter)));
|
|
1359
|
+
}
|
|
1360
|
+
function meterAtChecked(beatInQuarters, meter) {
|
|
1361
|
+
if (!isMeterMap(meter)) {
|
|
1362
|
+
return meter;
|
|
1363
|
+
}
|
|
1364
|
+
const ts = _optionalChain([meter, 'access', _6 => _6[entryIndexOf(meter, beatInQuarters)], 'optionalAccess', _7 => _7.ts]);
|
|
1365
|
+
if (ts === void 0) {
|
|
1366
|
+
throw new InvalidInputError(
|
|
1367
|
+
`meters names no time signature in force at beat ${beatInQuarters}; a meter map must stay a non-empty run of changes while it is being read`
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
return ts;
|
|
1371
|
+
}
|
|
1372
|
+
function barStartBeat(beatInQuarters, meter) {
|
|
1373
|
+
assertFiniteNumber(beatInQuarters, "beat");
|
|
1374
|
+
return barStartChecked(beatInQuarters, readMeterData(meter));
|
|
1375
|
+
}
|
|
1376
|
+
function barStartChecked(beatInQuarters, meter) {
|
|
1377
|
+
if (isMeterMap(meter)) {
|
|
1378
|
+
return barStartOf(meter, beatInQuarters);
|
|
1379
|
+
}
|
|
1380
|
+
const barLen = barBeatsOf(meter);
|
|
1381
|
+
return Math.floor(beatInQuarters / barLen + BEAT_EPS) * barLen;
|
|
1382
|
+
}
|
|
1383
|
+
function barIndexAt(beatInQuarters, meter) {
|
|
1384
|
+
assertFiniteNumber(beatInQuarters, "beat");
|
|
1385
|
+
return barIndexChecked(beatInQuarters, readMeterData(meter));
|
|
1386
|
+
}
|
|
1387
|
+
function barIndexChecked(beatInQuarters, meter) {
|
|
1388
|
+
if (isMeterMap(meter)) {
|
|
1389
|
+
return barIndexOf(meter, beatInQuarters);
|
|
1390
|
+
}
|
|
1391
|
+
return Math.floor(beatInQuarters / barBeatsOf(meter) + BEAT_EPS);
|
|
1392
|
+
}
|
|
1393
|
+
function beatsPerBarAt(beatInQuarters, meter) {
|
|
1394
|
+
assertFiniteNumber(beatInQuarters, "beat");
|
|
1395
|
+
const data = readMeterData(meter);
|
|
1396
|
+
return isMeterMap(data) ? barLengthOf(data, beatInQuarters) : barBeatsOf(meterAt(beatInQuarters, meter));
|
|
1397
|
+
}
|
|
1398
|
+
function resolveMeters(opts, name = "meters") {
|
|
1399
|
+
assertRecord(opts, `${name} options`);
|
|
1400
|
+
if (opts.ts !== void 0 && opts.meters !== void 0) {
|
|
1401
|
+
throw new InvalidInputError(
|
|
1402
|
+
`${name} and ts name the same thing; give one or the other, not both`
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
for (const [field, value] of [
|
|
1406
|
+
["meters", opts.meters],
|
|
1407
|
+
["ts", opts.ts]
|
|
1408
|
+
]) {
|
|
1409
|
+
if (value === null) {
|
|
1410
|
+
throw new InvalidInputError(
|
|
1411
|
+
`${field === "meters" ? name : "ts"} must name a meter, not null`
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
const given = _nullishCoalesce(opts.meters, () => ( opts.ts));
|
|
1416
|
+
if (given === void 0) {
|
|
1417
|
+
return [{ startBeat: 0, ts: copyTimeSignature(DEFAULT_TS) }];
|
|
1418
|
+
}
|
|
1419
|
+
const resolved = toMeterData(given, opts.meters !== void 0 ? name : "ts");
|
|
1420
|
+
return isMeterMap(resolved) ? resolved : [{ startBeat: 0, ts: resolved }];
|
|
1421
|
+
}
|
|
1422
|
+
function pulsesPerBar(ts) {
|
|
1423
|
+
return pulseCountOf(readSignature(ts, "ts"));
|
|
1424
|
+
}
|
|
1425
|
+
function isGroupHead(grouping, pulseIndex) {
|
|
1426
|
+
let acc = 0;
|
|
1427
|
+
for (const g of grouping) {
|
|
1428
|
+
if (acc === pulseIndex) {
|
|
1429
|
+
return true;
|
|
1430
|
+
}
|
|
1431
|
+
acc += g;
|
|
1432
|
+
}
|
|
1433
|
+
return false;
|
|
1434
|
+
}
|
|
1435
|
+
function isUniformGrouping(grouping) {
|
|
1436
|
+
return grouping.every((entry) => entry === grouping[0]);
|
|
1437
|
+
}
|
|
1438
|
+
function beatToBarPosition(beatInQuarters, meter) {
|
|
1439
|
+
assertFiniteNumber(beatInQuarters, "beat");
|
|
1440
|
+
return barPositionChecked(beatInQuarters, readMeterData(meter));
|
|
1441
|
+
}
|
|
1442
|
+
function barPositionChecked(beatInQuarters, meter) {
|
|
1443
|
+
return {
|
|
1444
|
+
bar: barIndexChecked(beatInQuarters, meter),
|
|
1445
|
+
beat: beatInQuarters - barStartChecked(beatInQuarters, meter)
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function pulseBeats(ts) {
|
|
1449
|
+
return pulseBeatsOf(readSignature(ts, "ts"));
|
|
1450
|
+
}
|
|
1451
|
+
function barPositionToPulse(pos, meter) {
|
|
1452
|
+
const position = assertRecord(pos, "position");
|
|
1453
|
+
assertInteger(position.bar, "position.bar");
|
|
1454
|
+
assertFiniteNumber(position.beat, "position.beat");
|
|
1455
|
+
return pulseChecked(position, readMeterData(meter));
|
|
1456
|
+
}
|
|
1457
|
+
function pulseChecked(pos, meter) {
|
|
1458
|
+
return pos.beat / pulseBeatsOf(meterAtBarChecked(pos.bar, meter)) + 1;
|
|
1459
|
+
}
|
|
1460
|
+
function meterAtBarChecked(bar, meter) {
|
|
1461
|
+
if (!isMeterMap(meter)) {
|
|
1462
|
+
return meter;
|
|
1463
|
+
}
|
|
1464
|
+
assertFiniteNumber(bar, "position.bar");
|
|
1465
|
+
return meterAtChecked(beatOfBarIndex(meter, bar), meter);
|
|
1466
|
+
}
|
|
1467
|
+
function formatBarPosition(beatInQuarters, meter, decimals = 2) {
|
|
1468
|
+
assertInteger(decimals, "decimals", 0, 100);
|
|
1469
|
+
assertFiniteNumber(beatInQuarters, "beat");
|
|
1470
|
+
const resolved = readMeterData(meter);
|
|
1471
|
+
const position = barPositionChecked(beatInQuarters, resolved);
|
|
1472
|
+
const ts = meterAtChecked(beatInQuarters, resolved);
|
|
1473
|
+
const pulse = pulseChecked(position, ts);
|
|
1474
|
+
const rounded = Number(pulse.toFixed(decimals));
|
|
1475
|
+
const barLength = isMeterMap(resolved) ? barLengthOf(resolved, beatInQuarters) : barBeatsOf(resolved);
|
|
1476
|
+
const pulsesInBar = Math.ceil(barLength / pulseBeatsOf(ts) - BEAT_EPS);
|
|
1477
|
+
if (rounded >= pulsesInBar + 1) {
|
|
1478
|
+
return `${barIndexChecked(barStartChecked(beatInQuarters, resolved) + barLength, resolved) + 1}.1`;
|
|
1479
|
+
}
|
|
1480
|
+
const wholePulse = Math.floor(rounded);
|
|
1481
|
+
const fraction = Number((rounded - wholePulse).toFixed(decimals));
|
|
1482
|
+
return fraction === 0 ? `${position.bar + 1}.${wholePulse}` : `${position.bar + 1}.${wholePulse}+${fraction}`;
|
|
1483
|
+
}
|
|
1484
|
+
function barPositionToBeat(pos, meter) {
|
|
1485
|
+
const position = assertRecord(pos, "bar position");
|
|
1486
|
+
assertInteger(position.bar, "bar position bar");
|
|
1487
|
+
assertFiniteNumber(position.beat, "bar position beat");
|
|
1488
|
+
const resolved = readMeterData(meter);
|
|
1489
|
+
if (isMeterMap(resolved)) {
|
|
1490
|
+
return beatOfBarIndex(resolved, position.bar) + position.beat;
|
|
1491
|
+
}
|
|
1492
|
+
return position.bar * barBeatsOf(resolved) + position.beat;
|
|
1493
|
+
}
|
|
1494
|
+
function metricWeight(beatInQuarters, meter) {
|
|
1495
|
+
assertFiniteNumber(beatInQuarters, "beat");
|
|
1496
|
+
const resolved = readMeterData(meter);
|
|
1497
|
+
const ts = meterAtChecked(beatInQuarters, resolved);
|
|
1498
|
+
const beat = beatInQuarters - barStartChecked(beatInQuarters, resolved);
|
|
1499
|
+
const pulse = pulseBeatsOf(ts);
|
|
1500
|
+
if (!isMultiple(beat, pulse)) {
|
|
1501
|
+
return 0;
|
|
1502
|
+
}
|
|
1503
|
+
const pulses = pulseCountOf(ts);
|
|
1504
|
+
const pulseIndex = Math.round(beat / pulse) % pulses;
|
|
1505
|
+
if (pulseIndex === 0) {
|
|
1506
|
+
return 3;
|
|
1507
|
+
}
|
|
1508
|
+
const grouping = pulseGroupingOf(ts);
|
|
1509
|
+
if (grouping !== void 0 && (isAdditiveReading(ts) || !isUniformGrouping(grouping))) {
|
|
1510
|
+
return isGroupHead(grouping, pulseIndex) ? 2 : 1;
|
|
1511
|
+
}
|
|
1512
|
+
if (pulses % 2 === 0 && pulseIndex === pulses / 2) {
|
|
1513
|
+
return 2;
|
|
1514
|
+
}
|
|
1515
|
+
return 1;
|
|
1516
|
+
}
|
|
1517
|
+
function metricGridUnit(meter) {
|
|
1518
|
+
const resolved = readMeterData(meter);
|
|
1519
|
+
if (!isMeterMap(resolved)) {
|
|
1520
|
+
return pulseBeatsOf(resolved);
|
|
1521
|
+
}
|
|
1522
|
+
let unit = 0;
|
|
1523
|
+
for (const change of resolved) {
|
|
1524
|
+
unit = commonStep(unit, pulseBeatsOf(change.ts));
|
|
1525
|
+
}
|
|
1526
|
+
if (unit <= 0) {
|
|
1527
|
+
return pulseBeatsOf(DEFAULT_TS);
|
|
1528
|
+
}
|
|
1529
|
+
const origin = _nullishCoalesce(_optionalChain([resolved, 'access', _8 => _8[0], 'optionalAccess', _9 => _9.startBeat]), () => ( 0));
|
|
1530
|
+
let onChanges = unit;
|
|
1531
|
+
for (const change of resolved) {
|
|
1532
|
+
onChanges = commonStep(onChanges, Math.abs(change.startBeat - origin));
|
|
1533
|
+
}
|
|
1534
|
+
return onChanges * MAX_CHANGE_DIVISIONS >= unit - BEAT_EPS ? onChanges : unit;
|
|
1535
|
+
}
|
|
1536
|
+
function commonStep(a, b) {
|
|
1537
|
+
let longer = Math.max(a, b);
|
|
1538
|
+
let shorter = Math.min(a, b);
|
|
1539
|
+
for (let i = 0; i < 8 && shorter > BEAT_EPS; i += 1) {
|
|
1540
|
+
const remainder = longer - Math.floor(longer / shorter + BEAT_EPS) * shorter;
|
|
1541
|
+
longer = shorter;
|
|
1542
|
+
shorter = remainder > BEAT_EPS ? remainder : 0;
|
|
1543
|
+
}
|
|
1544
|
+
const smallest = a > 0 && b > 0 ? Math.min(a, b) : Math.max(a, b);
|
|
1545
|
+
return longer > BEAT_EPS && longer <= smallest + BEAT_EPS ? longer : smallest;
|
|
1546
|
+
}
|
|
1547
|
+
function isStrongBeat(beatInQuarters, meter) {
|
|
1548
|
+
return metricWeight(beatInQuarters, meter) >= 2;
|
|
1549
|
+
}
|
|
1550
|
+
function tuplet(totalBeats, count) {
|
|
1551
|
+
assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, "tuplet total beats");
|
|
1552
|
+
assertPositiveInt(count, "tuplet count");
|
|
1553
|
+
return new Array(count).fill(totalBeats / count);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
|
|
1572
|
+
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
|
|
1001
1577
|
|
|
1002
1578
|
|
|
1003
1579
|
|
|
@@ -1060,5 +1636,5 @@ function isConsonantInterval(semitones, twoVoice = true) {
|
|
|
1060
1636
|
|
|
1061
1637
|
|
|
1062
1638
|
|
|
1063
|
-
exports.parseFailure = parseFailure; exports.unwrapParse = unwrapParse; exports.InvalidInputError = InvalidInputError; exports.NoSolutionError = NoSolutionError; exports.BudgetExceededError = BudgetExceededError; exports.isLibcantusError = isLibcantusError; exports.
|
|
1064
|
-
//# sourceMappingURL=chunk-
|
|
1639
|
+
exports.parseFailure = parseFailure; exports.unwrapParse = unwrapParse; exports.InvalidInputError = InvalidInputError; exports.NoSolutionError = NoSolutionError; exports.BudgetExceededError = BudgetExceededError; exports.isLibcantusError = isLibcantusError; exports.ARTICULATIONS = ARTICULATIONS; exports.BEAT_EPS = BEAT_EPS; exports.DEFAULT_GENERATION_BUDGET = DEFAULT_GENERATION_BUDGET; exports.describeRejected = describeRejected; exports.assertFiniteNumber = assertFiniteNumber; exports.assertInteger = assertInteger; exports.assertMidiPitch = assertMidiPitch; exports.assertPitchClass = assertPitchClass; exports.clampToMidi = clampToMidi; exports.assertDegree = assertDegree; exports.assertFiniteSemitones = assertFiniteSemitones; exports.dropSilentNotes = dropSilentNotes; exports.soundingNotesOnly = soundingNotesOnly; exports.assertArray = assertArray; exports.assertRecord = assertRecord; exports.assertOptions = assertOptions; exports.assertFlag = assertFlag; exports.assertFunction = assertFunction; exports.assertOneOf = assertOneOf; exports.assertPositiveInt = assertPositiveInt; exports.assertRange = assertRange; exports.assertGenerationBudget = assertGenerationBudget; exports.allocateCandidateTable = allocateCandidateTable; exports.allocateChoiceTable = allocateChoiceTable; exports.assertTimeSignature = assertTimeSignature; exports.assertMeterMap = assertMeterMap; exports.assertNoteEvent = assertNoteEvent; exports.assertNoteEvents = assertNoteEvents; exports.MAX_NAME_ACCIDENTALS = MAX_NAME_ACCIDENTALS; exports.maxNoteNameLength = maxNoteNameLength; exports.detectNoteNameSystem = detectNoteNameSystem; exports.pitchClassOf = pitchClassOf; exports.diatonicLetterOf = diatonicLetterOf; exports.naturalPitchClassOf = naturalPitchClassOf; exports.parseNote = parseNote; exports.tryParseNote = tryParseNote; exports.formatNote = formatNote; exports.parseKeyName = parseKeyName; exports.tryParseKeyName = tryParseKeyName; exports.formatKeyName = formatKeyName; exports.noteToPitchClass = noteToPitchClass; exports.noteToMidi = noteToMidi; exports.midiToNote = midiToNote; exports.transposeNote = transposeNote; exports.intervalSemitones = intervalSemitones; exports.parseInterval = parseInterval; exports.tryParseInterval = tryParseInterval; exports.toSpelledInterval = toSpelledInterval; exports.toNoteData = toNoteData; exports.transposeByInterval = transposeByInterval; exports.spelledInterval = spelledInterval; exports.ConsonanceClass = ConsonanceClass; exports.classifyInterval = classifyInterval; exports.isPerfectInterval = isPerfectInterval; exports.isConsonantInterval = isConsonantInterval; exports.toMeterData = toMeterData; exports.parseTimeSignature = parseTimeSignature; exports.tryParseTimeSignature = tryParseTimeSignature; exports.formatTimeSignature = formatTimeSignature; exports.isCompound = isCompound; exports.beatsPerBar = beatsPerBar; exports.meterAt = meterAt; exports.barStartBeat = barStartBeat; exports.barIndexAt = barIndexAt; exports.beatsPerBarAt = beatsPerBarAt; exports.resolveMeters = resolveMeters; exports.pulsesPerBar = pulsesPerBar; exports.beatToBarPosition = beatToBarPosition; exports.pulseBeats = pulseBeats; exports.barPositionToPulse = barPositionToPulse; exports.formatBarPosition = formatBarPosition; exports.barPositionToBeat = barPositionToBeat; exports.metricWeight = metricWeight; exports.metricGridUnit = metricGridUnit; exports.isStrongBeat = isStrongBeat; exports.tuplet = tuplet;
|
|
1640
|
+
//# sourceMappingURL=chunk-S5BKZZGM.cjs.map
|