@onjmin/dtm 0.1.8 → 0.1.9
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 +48 -3
- package/dist/index.d.mts +30 -47
- package/dist/index.d.ts +30 -47
- package/dist/index.js +748 -62
- package/dist/index.mjs +748 -62
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -159,18 +159,534 @@ async function buildNameToKeyMapping() {
|
|
|
159
159
|
return nameToKey;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// node_modules/.pnpm/@onjmin+chord-parser@1.0.2/node_modules/@onjmin/chord-parser/dist/index.mjs
|
|
163
|
+
var SHARP_NAMES = [
|
|
164
|
+
"C",
|
|
165
|
+
"C#",
|
|
166
|
+
"D",
|
|
167
|
+
"D#",
|
|
168
|
+
"E",
|
|
169
|
+
"F",
|
|
170
|
+
"F#",
|
|
171
|
+
"G",
|
|
172
|
+
"G#",
|
|
173
|
+
"A",
|
|
174
|
+
"A#",
|
|
175
|
+
"B"
|
|
176
|
+
];
|
|
177
|
+
var FLAT_NAMES = [
|
|
178
|
+
"C",
|
|
179
|
+
"Db",
|
|
180
|
+
"D",
|
|
181
|
+
"Eb",
|
|
182
|
+
"E",
|
|
183
|
+
"F",
|
|
184
|
+
"Gb",
|
|
185
|
+
"G",
|
|
186
|
+
"Ab",
|
|
187
|
+
"A",
|
|
188
|
+
"Bb",
|
|
189
|
+
"B"
|
|
190
|
+
];
|
|
191
|
+
var toPitchClass = (n) => (n % 12 + 12) % 12;
|
|
192
|
+
var noteName = (pc, flat = false) => (flat ? FLAT_NAMES : SHARP_NAMES)[toPitchClass(pc)];
|
|
193
|
+
var SyntaxErrorWithPos = class extends Error {
|
|
194
|
+
constructor(input, msg) {
|
|
195
|
+
super(
|
|
196
|
+
`SyntaxError: ${msg}
|
|
197
|
+
input.idx: ${input.idx}
|
|
198
|
+
input.str: ${input.str}`
|
|
199
|
+
);
|
|
200
|
+
this.name = "ChordSyntaxError";
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
var err = (input, msg) => {
|
|
204
|
+
throw new SyntaxErrorWithPos(input, msg);
|
|
205
|
+
};
|
|
206
|
+
var Input = class _Input {
|
|
207
|
+
static nums = new Set("0123456789");
|
|
208
|
+
str;
|
|
209
|
+
nest;
|
|
210
|
+
idx;
|
|
211
|
+
constructor(str, nest = 0) {
|
|
212
|
+
this.str = str;
|
|
213
|
+
this.nest = nest;
|
|
214
|
+
this.idx = 0;
|
|
215
|
+
}
|
|
216
|
+
get isEOF() {
|
|
217
|
+
return this.str.length <= this.idx;
|
|
218
|
+
}
|
|
219
|
+
get char() {
|
|
220
|
+
return this.str[this.idx];
|
|
221
|
+
}
|
|
222
|
+
/** 先頭の連続する数字を消費して数値で返す(破壊的)。数字が無ければ null。 */
|
|
223
|
+
get num() {
|
|
224
|
+
let str = "";
|
|
225
|
+
while (!this.isEOF) {
|
|
226
|
+
const char = this.char;
|
|
227
|
+
if (!_Input.nums.has(char)) break;
|
|
228
|
+
str += char;
|
|
229
|
+
this.idx++;
|
|
230
|
+
}
|
|
231
|
+
return str.length ? Number(str) : null;
|
|
232
|
+
}
|
|
233
|
+
slice(i) {
|
|
234
|
+
return this.str.slice(this.idx, this.idx + i);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var Output = class {
|
|
238
|
+
pitch = null;
|
|
239
|
+
chord = null;
|
|
240
|
+
isChord = false;
|
|
241
|
+
pending = null;
|
|
242
|
+
nest = -1;
|
|
243
|
+
get value() {
|
|
244
|
+
const { pitch, chord } = this;
|
|
245
|
+
return new Set(
|
|
246
|
+
[...chord].map((v) => v + pitch)
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
set value(chord) {
|
|
250
|
+
const pitch = this.pitch;
|
|
251
|
+
this.chord = new Set([...chord].map((v) => v - pitch));
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
var Matcher = class {
|
|
255
|
+
map = /* @__PURE__ */ new Map();
|
|
256
|
+
/** キー長を降順で保持(最長一致のため)。 */
|
|
257
|
+
lengths = [];
|
|
258
|
+
_set(key, value) {
|
|
259
|
+
this.map.set(key, value);
|
|
260
|
+
if (!this.lengths.includes(key.length)) {
|
|
261
|
+
this.lengths.push(key.length);
|
|
262
|
+
this.lengths.sort((a, b) => b - a);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
set(key, value) {
|
|
266
|
+
if (Array.isArray(key)) for (const k of key) this._set(k, value);
|
|
267
|
+
else this._set(key, value);
|
|
268
|
+
}
|
|
269
|
+
parse(input) {
|
|
270
|
+
for (const i of this.lengths) {
|
|
271
|
+
const s = input.slice(i);
|
|
272
|
+
if (this.map.has(s)) {
|
|
273
|
+
input.idx += s.length;
|
|
274
|
+
return this.map.get(s);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var BRACKET_START = 0;
|
|
281
|
+
var BRACKET_END = 1;
|
|
282
|
+
var COMMA = 2;
|
|
283
|
+
var DIVIDE = 3;
|
|
284
|
+
var formulaMatcher = new Matcher();
|
|
285
|
+
formulaMatcher.set("(", BRACKET_START);
|
|
286
|
+
formulaMatcher.set(")", BRACKET_END);
|
|
287
|
+
formulaMatcher.set(",", COMMA);
|
|
288
|
+
formulaMatcher.set(["/", "on"], DIVIDE);
|
|
289
|
+
var parseFormula = (input, output = new Output(), nest = 0) => {
|
|
290
|
+
let start = input.idx;
|
|
291
|
+
const _eval = (idx) => {
|
|
292
|
+
const str = input.str.slice(start, idx);
|
|
293
|
+
if (str.length) parseTerm(new Input(str, nest), output);
|
|
294
|
+
};
|
|
295
|
+
while (true) {
|
|
296
|
+
const { idx } = input;
|
|
297
|
+
if (input.isEOF) {
|
|
298
|
+
if (nest) err(input, `Unclosed ${nest} brackets`);
|
|
299
|
+
_eval(idx);
|
|
300
|
+
return output;
|
|
301
|
+
}
|
|
302
|
+
const res = formulaMatcher.parse(input);
|
|
303
|
+
if (res === null) {
|
|
304
|
+
input.idx++;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const { pending } = output;
|
|
308
|
+
_eval(idx);
|
|
309
|
+
switch (res) {
|
|
310
|
+
case BRACKET_START:
|
|
311
|
+
parseFormula(input, output, nest + 1);
|
|
312
|
+
break;
|
|
313
|
+
case BRACKET_END:
|
|
314
|
+
if (nest - 1 < 0) err(input, "Unable to close brackets");
|
|
315
|
+
return output;
|
|
316
|
+
case COMMA:
|
|
317
|
+
output.pending = pending;
|
|
318
|
+
break;
|
|
319
|
+
case DIVIDE: {
|
|
320
|
+
const o = parseFormula(input, new Output(), nest);
|
|
321
|
+
const v = [...output.value];
|
|
322
|
+
if (o.isChord) {
|
|
323
|
+
output.value = [...o.value].concat(v);
|
|
324
|
+
} else {
|
|
325
|
+
const a = v.sort((x, y) => x - y);
|
|
326
|
+
const pitch = (o.pitch + 3) % 12 - 3;
|
|
327
|
+
if (a[0] < pitch) {
|
|
328
|
+
while (a[0] < pitch) a.push(a.shift() + 12);
|
|
329
|
+
} else {
|
|
330
|
+
while (true) {
|
|
331
|
+
const w = a[a.length - 1] - 12;
|
|
332
|
+
if (w < pitch) break;
|
|
333
|
+
a.pop();
|
|
334
|
+
a.unshift(w);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
a.push(pitch);
|
|
338
|
+
output.value = a;
|
|
339
|
+
}
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
start = input.idx;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
var parseTerm = (input, output) => {
|
|
347
|
+
if (input.isEOF) return output;
|
|
348
|
+
if (output.pitch === null) return parsePitch(input, output);
|
|
349
|
+
if (output.pending === null) return parseFunc(input, output);
|
|
350
|
+
return parsePending(input, output);
|
|
351
|
+
};
|
|
352
|
+
var halfMatcher = new Matcher();
|
|
353
|
+
var halfMatcherStrict = new Matcher();
|
|
354
|
+
for (const m of [halfMatcher, halfMatcherStrict]) {
|
|
355
|
+
m.set(["#", "\u266F"], 1);
|
|
356
|
+
m.set(["b", "\u266D"], -1);
|
|
357
|
+
}
|
|
358
|
+
halfMatcher.set("+", 1);
|
|
359
|
+
halfMatcher.set("-", -1);
|
|
360
|
+
var parseHalf = (input, isPitch = false) => (isPitch ? halfMatcherStrict : halfMatcher).parse(input);
|
|
361
|
+
var idx2pitch = [0, 2, 4, 5, 7, 9, 11];
|
|
362
|
+
for (const i of [...idx2pitch.keys()]) idx2pitch.push(idx2pitch[i] + 12);
|
|
363
|
+
var deg2pitch = (deg) => idx2pitch[deg - 1];
|
|
364
|
+
var pitchMatcher = new Matcher();
|
|
365
|
+
for (const [i, v] of [..."CDEFGAB"].entries())
|
|
366
|
+
pitchMatcher.set(v, idx2pitch[i]);
|
|
367
|
+
var parsePitch = (input, output) => {
|
|
368
|
+
const pitch = pitchMatcher.parse(input);
|
|
369
|
+
if (pitch === null) err(input, "Not found pitch");
|
|
370
|
+
output.pitch = pitch;
|
|
371
|
+
const half = parseHalf(input, true);
|
|
372
|
+
if (half !== null) output.pitch += half;
|
|
373
|
+
return parseBase(input, output);
|
|
374
|
+
};
|
|
375
|
+
var MAJOR = [0, 4, 7];
|
|
376
|
+
var DIM = [0, 3, 6];
|
|
377
|
+
var baseMatcher = new Matcher();
|
|
378
|
+
baseMatcher.set(["m", "min", "Min", "minor", "Minor", "-"], [0, 3, 7]);
|
|
379
|
+
baseMatcher.set(["dim", "\u3007"], DIM);
|
|
380
|
+
baseMatcher.set("+", [0, 4, 8]);
|
|
381
|
+
baseMatcher.set(["\u03A6", "\u03C6", "\xF8"], [0, 3, 6, 10]);
|
|
382
|
+
var parseBase = (input, output) => {
|
|
383
|
+
const isMajMarker = /^maj/i.test(input.str.slice(input.idx));
|
|
384
|
+
const res = isMajMarker ? null : baseMatcher.parse(input);
|
|
385
|
+
if (res !== null) output.isChord = true;
|
|
386
|
+
output.chord = new Set(res || MAJOR);
|
|
387
|
+
if (res === DIM) {
|
|
388
|
+
const { num } = input;
|
|
389
|
+
const chord = output.chord;
|
|
390
|
+
if (num !== null) chord.add(deg2pitch(num) - 2);
|
|
391
|
+
}
|
|
392
|
+
output.nest = input.nest;
|
|
393
|
+
return parseTerm(input, output);
|
|
394
|
+
};
|
|
395
|
+
var add = (chord, n, half) => {
|
|
396
|
+
chord.add(deg2pitch(n) + half);
|
|
397
|
+
};
|
|
398
|
+
var aug = (chord) => {
|
|
399
|
+
chord.delete(deg2pitch(5));
|
|
400
|
+
chord.add(deg2pitch(5) + 1);
|
|
401
|
+
};
|
|
402
|
+
var _7th = (chord, n, _half2, isFlat = false) => {
|
|
403
|
+
if (n === 5) chord.delete(deg2pitch(3));
|
|
404
|
+
else if (n === 6) chord.add(deg2pitch(6));
|
|
405
|
+
else if (n === 69) chord.add(deg2pitch(6)).add(deg2pitch(9));
|
|
406
|
+
else {
|
|
407
|
+
if (n >= 7) chord.add(deg2pitch(7) + (isFlat ? -1 : 0));
|
|
408
|
+
if (n >= 9) chord.add(deg2pitch(9));
|
|
409
|
+
if (n >= 11) chord.add(deg2pitch(11));
|
|
410
|
+
if (n >= 13) chord.add(deg2pitch(13));
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
var _half = (chord, n, half) => {
|
|
414
|
+
chord.delete(deg2pitch(n));
|
|
415
|
+
chord.add(deg2pitch(n) + half);
|
|
416
|
+
};
|
|
417
|
+
var funcMatcher = new Matcher();
|
|
418
|
+
funcMatcher.set("add", add);
|
|
419
|
+
funcMatcher.set(["omit", "no"], (chord, n, half) => {
|
|
420
|
+
chord.delete(deg2pitch(n) + half);
|
|
421
|
+
});
|
|
422
|
+
funcMatcher.set("sus", (chord, n, half) => {
|
|
423
|
+
chord.delete(deg2pitch(3));
|
|
424
|
+
chord.add(deg2pitch(n) + half);
|
|
425
|
+
});
|
|
426
|
+
funcMatcher.set(
|
|
427
|
+
["M", "maj", "Maj", "major", "Major", "\u25B3", "\u0394"],
|
|
428
|
+
_7th
|
|
429
|
+
);
|
|
430
|
+
funcMatcher.set("aug", aug);
|
|
431
|
+
var parseFunc = (input, output) => {
|
|
432
|
+
if (!output.isChord) output.isChord = true;
|
|
433
|
+
const func = funcMatcher.parse(input);
|
|
434
|
+
const chord = output.chord;
|
|
435
|
+
if (func === null) {
|
|
436
|
+
const isAug = input.char === "+";
|
|
437
|
+
const half = parseHalf(input);
|
|
438
|
+
const { num } = input;
|
|
439
|
+
if (num === null) {
|
|
440
|
+
if (isAug) aug(chord);
|
|
441
|
+
else err(input, "Not found number");
|
|
442
|
+
}
|
|
443
|
+
if (half === null) {
|
|
444
|
+
if (input.nest === output.nest) _7th(chord, num, 0, true);
|
|
445
|
+
else add(chord, num, 0);
|
|
446
|
+
} else {
|
|
447
|
+
_half(chord, num, half);
|
|
448
|
+
}
|
|
449
|
+
} else if (func === aug) {
|
|
450
|
+
aug(chord);
|
|
451
|
+
} else {
|
|
452
|
+
output.pending = func;
|
|
453
|
+
}
|
|
454
|
+
return parseTerm(input, output);
|
|
455
|
+
};
|
|
456
|
+
var parsePending = (input, output) => {
|
|
457
|
+
const half = parseHalf(input);
|
|
458
|
+
const { num } = input;
|
|
459
|
+
const { pending, chord } = output;
|
|
460
|
+
if (num === null) err(input, "Not found number");
|
|
461
|
+
pending(
|
|
462
|
+
chord,
|
|
463
|
+
num,
|
|
464
|
+
half === null ? 0 : half
|
|
465
|
+
);
|
|
466
|
+
output.pending = null;
|
|
467
|
+
return parseTerm(input, output);
|
|
468
|
+
};
|
|
469
|
+
var parseChord = (symbol) => {
|
|
470
|
+
const output = parseFormula(new Input(symbol));
|
|
471
|
+
const notes = [...output.value].sort((a, b) => a - b);
|
|
472
|
+
const intervals = [...output.chord].sort((a, b) => a - b);
|
|
473
|
+
const pitchClasses = [...new Set(notes.map(toPitchClass))].sort(
|
|
474
|
+
(a, b) => a - b
|
|
475
|
+
);
|
|
476
|
+
return {
|
|
477
|
+
symbol,
|
|
478
|
+
root: toPitchClass(output.pitch),
|
|
479
|
+
notes,
|
|
480
|
+
pitchClasses,
|
|
481
|
+
intervals
|
|
482
|
+
};
|
|
483
|
+
};
|
|
484
|
+
var QUALITY_SOURCE = [
|
|
485
|
+
"",
|
|
486
|
+
// major
|
|
487
|
+
"m",
|
|
488
|
+
// minor
|
|
489
|
+
"7",
|
|
490
|
+
// dominant 7th
|
|
491
|
+
"M7",
|
|
492
|
+
// major 7th
|
|
493
|
+
"m7",
|
|
494
|
+
// minor 7th
|
|
495
|
+
"dim",
|
|
496
|
+
// diminished triad
|
|
497
|
+
"m7b5",
|
|
498
|
+
// half-diminished
|
|
499
|
+
"aug",
|
|
500
|
+
// augmented triad
|
|
501
|
+
"6",
|
|
502
|
+
// major 6th
|
|
503
|
+
"m6",
|
|
504
|
+
// minor 6th
|
|
505
|
+
"sus4",
|
|
506
|
+
"sus2",
|
|
507
|
+
"mM7",
|
|
508
|
+
// minor major 7th
|
|
509
|
+
"dim7",
|
|
510
|
+
// diminished 7th
|
|
511
|
+
"7sus4",
|
|
512
|
+
"7#5",
|
|
513
|
+
// augmented 7th
|
|
514
|
+
"add9",
|
|
515
|
+
"madd9",
|
|
516
|
+
"9",
|
|
517
|
+
"M9",
|
|
518
|
+
"m9",
|
|
519
|
+
"69",
|
|
520
|
+
"m69",
|
|
521
|
+
"5"
|
|
522
|
+
// power chord
|
|
523
|
+
];
|
|
524
|
+
var QUALITIES = QUALITY_SOURCE.map(
|
|
525
|
+
(quality, priority) => ({
|
|
526
|
+
quality,
|
|
527
|
+
pitchClasses: parseChord(`C${quality}`).pitchClasses,
|
|
528
|
+
priority
|
|
529
|
+
})
|
|
530
|
+
);
|
|
531
|
+
var QUALITY_BY_PCSET = (() => {
|
|
532
|
+
const map = /* @__PURE__ */ new Map();
|
|
533
|
+
for (const def of QUALITIES) {
|
|
534
|
+
const key = def.pitchClasses.join(",");
|
|
535
|
+
if (!map.has(key)) map.set(key, def);
|
|
536
|
+
}
|
|
537
|
+
return map;
|
|
538
|
+
})();
|
|
539
|
+
var detectChord = (notes, options = {}) => {
|
|
540
|
+
if (!notes.length) return [];
|
|
541
|
+
const { flat = false } = options;
|
|
542
|
+
const pcs = [...new Set(notes.map(toPitchClass))].sort((a, b) => a - b);
|
|
543
|
+
const bass = toPitchClass(
|
|
544
|
+
options.bass ?? notes.reduce((m, v) => Math.min(m, v), notes[0])
|
|
545
|
+
);
|
|
546
|
+
const scored = [];
|
|
547
|
+
for (const root of pcs) {
|
|
548
|
+
const relSet = new Set(pcs.map((pc) => toPitchClass(pc - root)));
|
|
549
|
+
for (const def of QUALITY_BY_PCSET.values()) {
|
|
550
|
+
let matchCount = 0;
|
|
551
|
+
let hasRoot = false;
|
|
552
|
+
for (const pc of def.pitchClasses) {
|
|
553
|
+
if (relSet.has(pc)) {
|
|
554
|
+
matchCount++;
|
|
555
|
+
if (pc === 0) hasRoot = true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (!hasRoot) continue;
|
|
559
|
+
if (matchCount < Math.min(2, def.pitchClasses.length)) continue;
|
|
560
|
+
const missingCount = def.pitchClasses.length - matchCount;
|
|
561
|
+
let extraCount = 0;
|
|
562
|
+
const defSet = new Set(def.pitchClasses);
|
|
563
|
+
for (const pc of relSet) {
|
|
564
|
+
if (!defSet.has(pc)) {
|
|
565
|
+
extraCount++;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const score = matchCount * 2 - missingCount * 1 - extraCount * 0.5;
|
|
569
|
+
const rootSymbol = noteName(root, flat) + def.quality;
|
|
570
|
+
const inversion = root !== bass;
|
|
571
|
+
scored.push({
|
|
572
|
+
score,
|
|
573
|
+
priority: def.priority,
|
|
574
|
+
candidate: {
|
|
575
|
+
symbol: inversion ? `${rootSymbol}/${noteName(bass, flat)}` : rootSymbol,
|
|
576
|
+
rootSymbol,
|
|
577
|
+
root,
|
|
578
|
+
quality: def.quality,
|
|
579
|
+
bass,
|
|
580
|
+
inversion
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
scored.sort((a, b) => {
|
|
586
|
+
if (Math.abs(a.score - b.score) > 1e-3) return b.score - a.score;
|
|
587
|
+
if (a.candidate.inversion !== b.candidate.inversion)
|
|
588
|
+
return a.candidate.inversion ? 1 : -1;
|
|
589
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
590
|
+
return a.candidate.root - b.candidate.root;
|
|
591
|
+
});
|
|
592
|
+
return scored.map((s) => s.candidate);
|
|
593
|
+
};
|
|
594
|
+
var toneRoleWeight = (rel) => {
|
|
595
|
+
if (rel === 0) return 1.3;
|
|
596
|
+
if (rel === 3 || rel === 4) return 1.2;
|
|
597
|
+
if (rel === 10 || rel === 11) return 0.95;
|
|
598
|
+
if (rel === 6 || rel === 7 || rel === 8) return 0.7;
|
|
599
|
+
return 0.85;
|
|
600
|
+
};
|
|
601
|
+
var CHORD_TEMPLATES = (() => {
|
|
602
|
+
const templates = [];
|
|
603
|
+
for (let root = 0; root < 12; root++) {
|
|
604
|
+
for (const def of QUALITIES) {
|
|
605
|
+
const pcs = /* @__PURE__ */ new Set();
|
|
606
|
+
const weights = new Array(12).fill(0);
|
|
607
|
+
const rel = /* @__PURE__ */ new Set();
|
|
608
|
+
for (const relPc of def.pitchClasses) {
|
|
609
|
+
rel.add(relPc);
|
|
610
|
+
const pc = toPitchClass(relPc + root);
|
|
611
|
+
pcs.add(pc);
|
|
612
|
+
weights[pc] = toneRoleWeight(relPc);
|
|
613
|
+
}
|
|
614
|
+
templates.push({
|
|
615
|
+
root,
|
|
616
|
+
quality: def.quality,
|
|
617
|
+
priority: def.priority,
|
|
618
|
+
pcs,
|
|
619
|
+
weights,
|
|
620
|
+
rel
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return templates;
|
|
625
|
+
})();
|
|
626
|
+
var toHan = (str) => str.replace(/[!-~]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 65248)).replace(/ /g, " ");
|
|
627
|
+
var parseChords = (str, bpm = 120) => {
|
|
628
|
+
const output = [];
|
|
629
|
+
const secBar = 60 / bpm * 4;
|
|
630
|
+
const frontChars = new Set("ABCDEFG_=%N");
|
|
631
|
+
let idx = 0;
|
|
632
|
+
let last = null;
|
|
633
|
+
for (const line of toHan(str).split("\n").map((v) => v.trim())) {
|
|
634
|
+
if (!line.length || /^#/.test(line)) continue;
|
|
635
|
+
for (const str2 of line.split(/[|ll→]/)) {
|
|
636
|
+
if (!str2.length) continue;
|
|
637
|
+
const when = idx++ * secBar;
|
|
638
|
+
const a = [];
|
|
639
|
+
for (let i = 0; i < str2.length; i++) {
|
|
640
|
+
const char = str2[i];
|
|
641
|
+
const prev = str2[i - 1];
|
|
642
|
+
const prev2 = str2.slice(i - 2, i);
|
|
643
|
+
if (!frontChars.has(char)) continue;
|
|
644
|
+
if (prev === "/" || prev2 === "on") continue;
|
|
645
|
+
if (prev2 === "N." && char === "C") continue;
|
|
646
|
+
a.push(i);
|
|
647
|
+
}
|
|
648
|
+
if (!a.length) continue;
|
|
649
|
+
const divide = 2 ** Math.ceil(Math.log2(a.length));
|
|
650
|
+
const unitTime = secBar / divide;
|
|
651
|
+
for (const [i, v] of a.entries()) {
|
|
652
|
+
const s = str2.slice(v, i === a.length - 1 ? str2.length : a[i + 1]).replace(/\s+/g, "");
|
|
653
|
+
const c = s[0];
|
|
654
|
+
if (c === "_" || c === "N") {
|
|
655
|
+
last = null;
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (c === "=") {
|
|
659
|
+
if (last) last.duration += unitTime;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
const _when = when + i * unitTime;
|
|
663
|
+
if (c === "%") {
|
|
664
|
+
if (last === null) continue;
|
|
665
|
+
const base = last;
|
|
666
|
+
last = { ...base, when: _when, duration: unitTime };
|
|
667
|
+
} else {
|
|
668
|
+
const key = s.slice(0, s[1] === "#" ? 2 : 1);
|
|
669
|
+
const chord = s.slice(key.length).replace(/[\s・]/g, "");
|
|
670
|
+
last = {
|
|
671
|
+
key,
|
|
672
|
+
chord,
|
|
673
|
+
when: _when,
|
|
674
|
+
duration: unitTime
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
output.push(last);
|
|
678
|
+
}
|
|
679
|
+
if (last !== null && divide > a.length)
|
|
680
|
+
last.duration += unitTime * (divide - a.length);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return output;
|
|
684
|
+
};
|
|
685
|
+
|
|
162
686
|
// src/chords.ts
|
|
163
687
|
var C3 = 48;
|
|
164
688
|
var buildChordPlacements = (options) => {
|
|
165
|
-
const {
|
|
166
|
-
chordStr,
|
|
167
|
-
patternType,
|
|
168
|
-
rootShift,
|
|
169
|
-
bpm,
|
|
170
|
-
stepsPerBar,
|
|
171
|
-
parseChord,
|
|
172
|
-
parseChords
|
|
173
|
-
} = options;
|
|
689
|
+
const { chordStr, patternType, rootShift, bpm, stepsPerBar } = options;
|
|
174
690
|
const placements = [];
|
|
175
691
|
if (!chordStr.trim()) return placements;
|
|
176
692
|
const offset = rootShift;
|
|
@@ -200,7 +716,7 @@ var buildChordPlacements = (options) => {
|
|
|
200
716
|
for (const chord of group) {
|
|
201
717
|
let notes;
|
|
202
718
|
try {
|
|
203
|
-
notes = [...parseChord(`${chord.key}${chord.chord}`).
|
|
719
|
+
notes = [...parseChord(`${chord.key}${chord.chord}`).notes];
|
|
204
720
|
} catch {
|
|
205
721
|
continue;
|
|
206
722
|
}
|
|
@@ -287,7 +803,7 @@ var buildChordPlacements = (options) => {
|
|
|
287
803
|
chordNames.forEach((chordName, barIndex) => {
|
|
288
804
|
let notes;
|
|
289
805
|
try {
|
|
290
|
-
notes = [...parseChord(chordName).
|
|
806
|
+
notes = [...parseChord(chordName).notes];
|
|
291
807
|
} catch {
|
|
292
808
|
return;
|
|
293
809
|
}
|
|
@@ -361,6 +877,7 @@ var buildUI = (target, options) => {
|
|
|
361
877
|
<button class="dtm-play" data-dtm="play" disabled>${icon("play")}<span>\u8A66\u8074</span></button>
|
|
362
878
|
<button class="dtm-iconbtn dtm-rec" data-dtm="rec" title="\u9332\u97F3">${icon("record")}</button>
|
|
363
879
|
<label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
|
|
880
|
+
<span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
|
|
364
881
|
<span class="dtm-grow"></span>
|
|
365
882
|
<span class="dtm-label">BPM</span>
|
|
366
883
|
<input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
|
|
@@ -453,10 +970,10 @@ var buildUI = (target, options) => {
|
|
|
453
970
|
<div class="dtm-row dtm-hidden" data-dtm="midi-track-selection"></div>
|
|
454
971
|
<div class="dtm-row">
|
|
455
972
|
<span class="dtm-label">MML</span>
|
|
456
|
-
<textarea class="dtm-textarea" data-dtm="mml-input" placeholder="MML\u3092\u5165\u529B"></textarea>
|
|
973
|
+
<textarea class="dtm-textarea dtm-grow" data-dtm="mml-input" placeholder="MML\u3092\u5165\u529B"></textarea>
|
|
974
|
+
<button class="dtm-btn dtm-btn--primary" data-dtm="mml-load">\u8AAD\u8FBC</button>
|
|
457
975
|
</div>
|
|
458
976
|
<div class="dtm-row">
|
|
459
|
-
<button class="dtm-btn dtm-btn--primary" data-dtm="mml-load">MML\u8AAD\u8FBC</button>
|
|
460
977
|
<span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
|
|
461
978
|
<select class="dtm-select" data-dtm="shift-select">
|
|
462
979
|
<option value="-96">-2\u5206</option>
|
|
@@ -530,6 +1047,8 @@ var buildUI = (target, options) => {
|
|
|
530
1047
|
const sel = (name) => q(root, `[data-dtm="${name}"]`);
|
|
531
1048
|
return {
|
|
532
1049
|
root,
|
|
1050
|
+
topbar: sel("transport"),
|
|
1051
|
+
topbarLoading: sel("topbar-loading"),
|
|
533
1052
|
playBtn: sel("play"),
|
|
534
1053
|
recBtn: sel("rec"),
|
|
535
1054
|
soloCheckbox: sel("solo"),
|
|
@@ -1747,8 +2266,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
|
|
|
1747
2266
|
))().then((v) => {
|
|
1748
2267
|
loaded.set(m, v);
|
|
1749
2268
|
return v;
|
|
1750
|
-
}).catch((
|
|
1751
|
-
console.warn(`[dtm] koe\u97F3\u6E90 "${m}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F`,
|
|
2269
|
+
}).catch((err2) => {
|
|
2270
|
+
console.warn(`[dtm] koe\u97F3\u6E90 "${m}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F`, err2);
|
|
1752
2271
|
return null;
|
|
1753
2272
|
});
|
|
1754
2273
|
loading.set(m, p);
|
|
@@ -3240,8 +3759,10 @@ var parseMML = (mml, options = {}) => {
|
|
|
3240
3759
|
numStr += body[j];
|
|
3241
3760
|
j++;
|
|
3242
3761
|
}
|
|
3243
|
-
if (ch === "t" &&
|
|
3244
|
-
bpm
|
|
3762
|
+
if (ch === "t" && numStr) {
|
|
3763
|
+
if (bpm === null) {
|
|
3764
|
+
bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
|
|
3765
|
+
}
|
|
3245
3766
|
}
|
|
3246
3767
|
pushTok("ctrl", currentStep, 0, tokStart);
|
|
3247
3768
|
} else if (ch === "[") {
|
|
@@ -3743,6 +4264,7 @@ var DAW_CSS = `
|
|
|
3743
4264
|
}
|
|
3744
4265
|
.dtm-input--num { width: 64px; text-align: center; font-size: 16px; }
|
|
3745
4266
|
.dtm-textarea { width: 100%; min-height: 56px; resize: vertical; line-height: 1.7; }
|
|
4267
|
+
.dtm-textarea.dtm-grow { width: 0; }
|
|
3746
4268
|
.dtm-range { height: var(--dtm-tap); accent-color: var(--dtm-primary); }
|
|
3747
4269
|
|
|
3748
4270
|
/* \u2500\u2500\u2500 \u30C8\u30E9\u30C3\u30AF\u30D4\u30EB\uFF08\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u9078\u629E\u30DC\u30BF\u30F3\uFF09 \u2500\u2500\u2500 */
|
|
@@ -3910,7 +4432,7 @@ var DAW_CSS = `
|
|
|
3910
4432
|
|
|
3911
4433
|
/* \u2500\u2500\u2500 \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4 \u2500\u2500\u2500 */
|
|
3912
4434
|
.dtm-overlay {
|
|
3913
|
-
position: absolute; inset: 0; z-index:
|
|
4435
|
+
position: absolute; inset: 0; z-index: 10;
|
|
3914
4436
|
background: rgba(0,0,0,.92);
|
|
3915
4437
|
display: flex; align-items: center; justify-content: center;
|
|
3916
4438
|
flex-direction: column; gap: 14px;
|
|
@@ -3959,6 +4481,22 @@ var DAW_CSS = `
|
|
|
3959
4481
|
letter-spacing: .15em;
|
|
3960
4482
|
min-height: 1em;
|
|
3961
4483
|
}
|
|
4484
|
+
.dtm-topbar-loading {
|
|
4485
|
+
display: none;
|
|
4486
|
+
font-family: var(--dtm-font);
|
|
4487
|
+
font-size: 11px;
|
|
4488
|
+
color: var(--dtm-primary);
|
|
4489
|
+
margin-left: 12px;
|
|
4490
|
+
letter-spacing: .15em;
|
|
4491
|
+
align-self: center;
|
|
4492
|
+
}
|
|
4493
|
+
.dtm-topbar.is-loading .dtm-topbar-loading {
|
|
4494
|
+
display: inline-block;
|
|
4495
|
+
}
|
|
4496
|
+
.dtm-topbar.is-loading {
|
|
4497
|
+
pointer-events: none;
|
|
4498
|
+
opacity: 0.7;
|
|
4499
|
+
}
|
|
3962
4500
|
|
|
3963
4501
|
@keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
|
3964
4502
|
.dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
|
|
@@ -4034,6 +4572,14 @@ var DAW_CSS = `
|
|
|
4034
4572
|
min-width: 2em;
|
|
4035
4573
|
margin-left: 4px;
|
|
4036
4574
|
}
|
|
4575
|
+
.dtm-player-chord {
|
|
4576
|
+
font-family: 'k8x12', monospace;
|
|
4577
|
+
font-size: 11px;
|
|
4578
|
+
color: var(--dtm-accent);
|
|
4579
|
+
min-width: 4em;
|
|
4580
|
+
margin-left: 8px;
|
|
4581
|
+
font-weight: bold;
|
|
4582
|
+
}
|
|
4037
4583
|
.dtm-player-dots {
|
|
4038
4584
|
margin-left: auto;
|
|
4039
4585
|
display: flex;
|
|
@@ -4384,7 +4930,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
4384
4930
|
const trackConfigs = options.tracks ?? DEFAULT_TRACKS;
|
|
4385
4931
|
const drumPatterns = options.drumPatterns ?? DRUM_PATTERNS;
|
|
4386
4932
|
const showMidi = !!options.parseMidi;
|
|
4387
|
-
const showChord =
|
|
4933
|
+
const showChord = true;
|
|
4388
4934
|
const refs = buildUI(target, {
|
|
4389
4935
|
tracks: trackConfigs,
|
|
4390
4936
|
drumPatternNames: Object.keys(drumPatterns),
|
|
@@ -5080,13 +5626,15 @@ var mountDAW = (target, options = {}) => {
|
|
|
5080
5626
|
const streaming = !!voices && streamTracks.some((t) => t.notes.length > 0);
|
|
5081
5627
|
if (streaming && voices) {
|
|
5082
5628
|
const overlay = showLoadingOverlay(refs.rollContainer);
|
|
5629
|
+
setLoading(true);
|
|
5083
5630
|
try {
|
|
5084
5631
|
await voices.loadModels(streamTracks.map((t) => t.model));
|
|
5085
5632
|
await voices.warm(streamTracks);
|
|
5086
|
-
} catch (
|
|
5087
|
-
console.warn("[dtm] voice preload failed",
|
|
5633
|
+
} catch (err2) {
|
|
5634
|
+
console.warn("[dtm] voice preload failed", err2);
|
|
5088
5635
|
} finally {
|
|
5089
5636
|
overlay.remove();
|
|
5637
|
+
setLoading(false);
|
|
5090
5638
|
}
|
|
5091
5639
|
}
|
|
5092
5640
|
if (playbackState !== "paused") {
|
|
@@ -5435,21 +5983,28 @@ var mountDAW = (target, options = {}) => {
|
|
|
5435
5983
|
barLimit: barLimitBars
|
|
5436
5984
|
};
|
|
5437
5985
|
}
|
|
5438
|
-
const trackLines =
|
|
5439
|
-
|
|
5440
|
-
)
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5986
|
+
const trackLines = [];
|
|
5987
|
+
const trackLinesMini = [];
|
|
5988
|
+
trackStates.forEach((t, i) => {
|
|
5989
|
+
const notes = clipNotes(t.core.getNotes());
|
|
5990
|
+
if (notes.length > 0) {
|
|
5991
|
+
const mml = t.core.getMMLFromNotes(notes, bpm, t.volume).trim();
|
|
5992
|
+
trackLines.push(`@${i} ${mml}`);
|
|
5993
|
+
trackLinesMini.push(`@${i}${mml.replace(/\s+/g, "")}`);
|
|
5994
|
+
}
|
|
5995
|
+
});
|
|
5444
5996
|
const lyricLines = trackStates.map((t, i) => ({
|
|
5445
5997
|
i,
|
|
5446
|
-
|
|
5998
|
+
notes: clipNotes(t.core.getNotes()),
|
|
5999
|
+
text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
|
|
5447
6000
|
model: t.lyricModel.trim(),
|
|
5448
6001
|
vol: t.vocalVolume,
|
|
5449
6002
|
gate: t.vocalGate,
|
|
5450
6003
|
pan: t.vocalPan,
|
|
5451
6004
|
oct: t.vocalOctave
|
|
5452
|
-
})).filter(
|
|
6005
|
+
})).filter(
|
|
6006
|
+
(x) => x.model.length > 0 && x.text.length > 0 && x.notes.length > 0
|
|
6007
|
+
).map((x) => {
|
|
5453
6008
|
const params = [
|
|
5454
6009
|
x.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x.vol}`,
|
|
5455
6010
|
x.gate === 100 ? "" : `q${x.gate}`,
|
|
@@ -5465,7 +6020,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
5465
6020
|
full,
|
|
5466
6021
|
minified,
|
|
5467
6022
|
ignoredCount: 0,
|
|
5468
|
-
trackCount:
|
|
6023
|
+
trackCount: trackLines.length,
|
|
5469
6024
|
barLimit: barLimitBars
|
|
5470
6025
|
};
|
|
5471
6026
|
};
|
|
@@ -5585,7 +6140,6 @@ var mountDAW = (target, options = {}) => {
|
|
|
5585
6140
|
updateUndoRedo();
|
|
5586
6141
|
};
|
|
5587
6142
|
const applyChord = () => {
|
|
5588
|
-
if (!options.parseChord || !options.parseChords) return;
|
|
5589
6143
|
const active = getActive();
|
|
5590
6144
|
const chordTrack = trackStates.find((t) => t.config.id === "chord");
|
|
5591
6145
|
if (!chordTrack) return;
|
|
@@ -5594,9 +6148,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
5594
6148
|
patternType: active.savedChordPattern,
|
|
5595
6149
|
rootShift: active.savedChordRoot,
|
|
5596
6150
|
bpm,
|
|
5597
|
-
stepsPerBar: renderConfig.stepsPerBar
|
|
5598
|
-
parseChord: options.parseChord,
|
|
5599
|
-
parseChords: options.parseChords
|
|
6151
|
+
stepsPerBar: renderConfig.stepsPerBar
|
|
5600
6152
|
});
|
|
5601
6153
|
chordTrack.core.clearNotesWithoutHistory();
|
|
5602
6154
|
chordTrack.core.beginBatch();
|
|
@@ -5681,9 +6233,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
5681
6233
|
};
|
|
5682
6234
|
const overlayDuring = (fn) => {
|
|
5683
6235
|
refs.overlay.hidden = false;
|
|
6236
|
+
setLoading(true);
|
|
5684
6237
|
setTimeout(() => {
|
|
5685
6238
|
fn();
|
|
5686
6239
|
refs.overlay.hidden = true;
|
|
6240
|
+
setLoading(false);
|
|
5687
6241
|
}, 30);
|
|
5688
6242
|
};
|
|
5689
6243
|
const wireEvents = () => {
|
|
@@ -5827,6 +6381,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
5827
6381
|
const file = refs.midiInput.files?.[0];
|
|
5828
6382
|
if (!file || !options.parseMidi) return;
|
|
5829
6383
|
refs.overlay.hidden = false;
|
|
6384
|
+
setLoading(true);
|
|
5830
6385
|
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
5831
6386
|
pendingMidi = options.parseMidi(buffer);
|
|
5832
6387
|
detectedTracks = analyzeMidiTracks(pendingMidi);
|
|
@@ -5847,6 +6402,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
5847
6402
|
});
|
|
5848
6403
|
refs.midiTrackSelection.classList.remove("dtm-hidden");
|
|
5849
6404
|
refs.overlay.hidden = true;
|
|
6405
|
+
setLoading(false);
|
|
5850
6406
|
});
|
|
5851
6407
|
refs.midiLoadBtn.addEventListener("click", () => {
|
|
5852
6408
|
if (!pendingMidi) return;
|
|
@@ -5920,6 +6476,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
5920
6476
|
resizeObserver.observe(refs.rollContainer);
|
|
5921
6477
|
document.addEventListener("pointermove", onPointerMove);
|
|
5922
6478
|
document.addEventListener("pointerup", onPointerUp);
|
|
6479
|
+
const setLoading = (loading) => {
|
|
6480
|
+
refs.topbar.classList.toggle("is-loading", loading);
|
|
6481
|
+
};
|
|
5923
6482
|
return {
|
|
5924
6483
|
play,
|
|
5925
6484
|
pause,
|
|
@@ -5957,6 +6516,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
5957
6516
|
exportMIDI: exportMIDI2,
|
|
5958
6517
|
setBpm,
|
|
5959
6518
|
getPlaybackState: () => playbackState,
|
|
6519
|
+
setLoading,
|
|
5960
6520
|
destroy: () => {
|
|
5961
6521
|
sequencer.stop();
|
|
5962
6522
|
resizeObserver.disconnect();
|
|
@@ -6101,6 +6661,117 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6101
6661
|
const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
|
|
6102
6662
|
(a, b) => a - b
|
|
6103
6663
|
);
|
|
6664
|
+
const trackStats = trackIndices.map((index) => {
|
|
6665
|
+
const trackPlacements = placements.filter((p) => p.trackIndex === index);
|
|
6666
|
+
if (trackPlacements.length === 0) {
|
|
6667
|
+
return { index, isChords: false, avgPitch: 0, noteCount: 0 };
|
|
6668
|
+
}
|
|
6669
|
+
const sumPitch = trackPlacements.reduce((sum, p) => sum + p.pitch, 0);
|
|
6670
|
+
const avgPitch = sumPitch / trackPlacements.length;
|
|
6671
|
+
const stepCounts = /* @__PURE__ */ new Map();
|
|
6672
|
+
for (const p of trackPlacements) {
|
|
6673
|
+
for (let s = p.startStep; s < p.startStep + p.durationSteps; s++) {
|
|
6674
|
+
stepCounts.set(s, (stepCounts.get(s) ?? 0) + 1);
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
const polyphonicSteps = Array.from(stepCounts.values()).filter(
|
|
6678
|
+
(c) => c >= 2
|
|
6679
|
+
).length;
|
|
6680
|
+
const isChords = polyphonicSteps > 0;
|
|
6681
|
+
return {
|
|
6682
|
+
index,
|
|
6683
|
+
isChords,
|
|
6684
|
+
avgPitch,
|
|
6685
|
+
noteCount: trackPlacements.length
|
|
6686
|
+
};
|
|
6687
|
+
});
|
|
6688
|
+
const chordTrackIndices = trackStats.filter((s) => s.isChords).map((s) => s.index);
|
|
6689
|
+
const nonChordTracks = trackStats.filter(
|
|
6690
|
+
(s) => !s.isChords && s.noteCount > 0
|
|
6691
|
+
);
|
|
6692
|
+
let bassTrackIndex = null;
|
|
6693
|
+
if (nonChordTracks.length > 0) {
|
|
6694
|
+
nonChordTracks.sort((a, b) => a.avgPitch - b.avgPitch);
|
|
6695
|
+
bassTrackIndex = nonChordTracks[0].index;
|
|
6696
|
+
}
|
|
6697
|
+
const priorityTrackIndices = /* @__PURE__ */ new Set();
|
|
6698
|
+
for (const idx of chordTrackIndices) {
|
|
6699
|
+
priorityTrackIndices.add(idx);
|
|
6700
|
+
}
|
|
6701
|
+
if (bassTrackIndex !== null) {
|
|
6702
|
+
priorityTrackIndices.add(bassTrackIndex);
|
|
6703
|
+
}
|
|
6704
|
+
const hasPriorityTracksInMml = priorityTrackIndices.size > 0;
|
|
6705
|
+
const maxStep = placements.reduce(
|
|
6706
|
+
(max, p) => Math.max(max, p.startStep + p.durationSteps),
|
|
6707
|
+
0
|
|
6708
|
+
);
|
|
6709
|
+
const priorityPitches = Array.from(
|
|
6710
|
+
{ length: maxStep + 1 },
|
|
6711
|
+
() => /* @__PURE__ */ new Set()
|
|
6712
|
+
);
|
|
6713
|
+
const allPitches = Array.from(
|
|
6714
|
+
{ length: maxStep + 1 },
|
|
6715
|
+
() => /* @__PURE__ */ new Set()
|
|
6716
|
+
);
|
|
6717
|
+
for (const p of placements) {
|
|
6718
|
+
for (let s = p.startStep; s < p.startStep + p.durationSteps; s++) {
|
|
6719
|
+
if (s >= 0 && s <= maxStep) {
|
|
6720
|
+
allPitches[s].add(p.pitch);
|
|
6721
|
+
if (priorityTrackIndices.has(p.trackIndex)) {
|
|
6722
|
+
priorityPitches[s].add(p.pitch);
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
const stepChords = [];
|
|
6728
|
+
const chordCache = /* @__PURE__ */ new Map();
|
|
6729
|
+
let lastChord = "";
|
|
6730
|
+
let hasPriorityStarted = false;
|
|
6731
|
+
const GRID_SIZE = 48;
|
|
6732
|
+
const MIN_DURATION = 12;
|
|
6733
|
+
for (let g = 0; g <= Math.ceil(maxStep / GRID_SIZE); g++) {
|
|
6734
|
+
const startS = g * GRID_SIZE;
|
|
6735
|
+
const endS = Math.min(maxStep, (g + 1) * GRID_SIZE - 1);
|
|
6736
|
+
if (startS > maxStep) break;
|
|
6737
|
+
for (let s = startS; s <= endS; s++) {
|
|
6738
|
+
const priorityPitchesAtStep = Array.from(priorityPitches[s]);
|
|
6739
|
+
if (priorityPitchesAtStep.length > 0) {
|
|
6740
|
+
hasPriorityStarted = true;
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6743
|
+
const pitchDurations = /* @__PURE__ */ new Map();
|
|
6744
|
+
for (let s = startS; s <= endS; s++) {
|
|
6745
|
+
const usePriority = hasPriorityStarted && hasPriorityTracksInMml;
|
|
6746
|
+
const pitches = usePriority ? Array.from(priorityPitches[s]) : Array.from(allPitches[s]);
|
|
6747
|
+
for (const p of pitches) {
|
|
6748
|
+
pitchDurations.set(p, (pitchDurations.get(p) ?? 0) + 1);
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6751
|
+
let activePitches = Array.from(pitchDurations.entries()).filter(([_, dur]) => dur >= MIN_DURATION).map(([p, _]) => p);
|
|
6752
|
+
if (activePitches.length === 0 && pitchDurations.size > 0) {
|
|
6753
|
+
const maxDur = Math.max(...pitchDurations.values());
|
|
6754
|
+
activePitches = Array.from(pitchDurations.entries()).filter(([_, dur]) => dur === maxDur).map(([p, _]) => p);
|
|
6755
|
+
}
|
|
6756
|
+
let gridChord = lastChord;
|
|
6757
|
+
if (activePitches.length > 0) {
|
|
6758
|
+
const sortedPitches = activePitches.sort((a, b) => a - b);
|
|
6759
|
+
const cacheKey = sortedPitches.join(",");
|
|
6760
|
+
if (chordCache.has(cacheKey)) {
|
|
6761
|
+
const chordName = chordCache.get(cacheKey);
|
|
6762
|
+
if (chordName) gridChord = chordName;
|
|
6763
|
+
} else {
|
|
6764
|
+
const candidates = detectChord(sortedPitches);
|
|
6765
|
+
const chordName = candidates[0]?.symbol ?? "";
|
|
6766
|
+
if (chordName) gridChord = chordName;
|
|
6767
|
+
chordCache.set(cacheKey, chordName);
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6770
|
+
for (let s = startS; s <= endS; s++) {
|
|
6771
|
+
stepChords[s] = gridChord;
|
|
6772
|
+
}
|
|
6773
|
+
lastChord = gridChord;
|
|
6774
|
+
}
|
|
6104
6775
|
const seqTracks = trackIndices.map((index) => {
|
|
6105
6776
|
let id = 0;
|
|
6106
6777
|
const notes = placements.filter((p) => p.trackIndex === index).map((p) => ({
|
|
@@ -6301,6 +6972,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6301
6972
|
barEl.className = "dtm-player-bar";
|
|
6302
6973
|
barEl.textContent = "-";
|
|
6303
6974
|
beatRow.appendChild(barEl);
|
|
6975
|
+
const chordEl = doc.createElement("span");
|
|
6976
|
+
chordEl.className = "dtm-player-chord";
|
|
6977
|
+
chordEl.textContent = "";
|
|
6978
|
+
beatRow.appendChild(chordEl);
|
|
6304
6979
|
const chips = [];
|
|
6305
6980
|
const makeChip = (label) => {
|
|
6306
6981
|
const chip = doc.createElement("span");
|
|
@@ -6444,10 +7119,20 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6444
7119
|
lane.scrollLeft = Math.max(0, Math.min(next, maxScroll));
|
|
6445
7120
|
};
|
|
6446
7121
|
const renderPlayhead = (step) => {
|
|
7122
|
+
const intStep = Math.floor(step);
|
|
6447
7123
|
const beatIndex = Math.floor(step / STEPS_PER_BEAT3) % 4;
|
|
6448
7124
|
for (let i = 0; i < 4; i++)
|
|
6449
7125
|
beatDots[i].classList.toggle("dtm-player-beat-dot--on", i === beatIndex);
|
|
6450
7126
|
barEl.textContent = String(Math.floor(step / STEPS_PER_BAR) + 1);
|
|
7127
|
+
const chordName = stepChords[intStep] ?? "";
|
|
7128
|
+
if (chordEl.textContent !== chordName) {
|
|
7129
|
+
chordEl.textContent = chordName;
|
|
7130
|
+
if (chordName) {
|
|
7131
|
+
console.log(
|
|
7132
|
+
`[dtm-player-chord] Active Chord: ${chordName} (step: ${intStep})`
|
|
7133
|
+
);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
6451
7136
|
for (const view of laneViews) {
|
|
6452
7137
|
let active = null;
|
|
6453
7138
|
for (const t of view.tokens) {
|
|
@@ -6461,6 +7146,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6461
7146
|
const resetPlayhead = () => {
|
|
6462
7147
|
for (const d of beatDots) d.classList.remove("dtm-player-beat-dot--on");
|
|
6463
7148
|
barEl.textContent = "-";
|
|
7149
|
+
chordEl.textContent = "";
|
|
6464
7150
|
for (const view of laneViews) {
|
|
6465
7151
|
for (const t of view.tokens) t.el.classList.remove("is-active");
|
|
6466
7152
|
view.lane.scrollLeft = 0;
|
|
@@ -6524,6 +7210,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6524
7210
|
});
|
|
6525
7211
|
}
|
|
6526
7212
|
return {
|
|
7213
|
+
id: String(index),
|
|
6527
7214
|
model: lt.model,
|
|
6528
7215
|
volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
|
|
6529
7216
|
pan: panToStereo(lt.pan ?? DEFAULT_PAN),
|
|
@@ -6539,15 +7226,19 @@ var mountMmlPlayer = (target, mml, options = {}) => {
|
|
|
6539
7226
|
try {
|
|
6540
7227
|
await v.loadModels(tracks.map((t) => t.model));
|
|
6541
7228
|
await v.warm(tracks);
|
|
6542
|
-
} catch (
|
|
6543
|
-
console.warn("[dtm] voice preload failed",
|
|
7229
|
+
} catch (err2) {
|
|
7230
|
+
console.warn("[dtm] voice preload failed", err2);
|
|
6544
7231
|
} finally {
|
|
6545
7232
|
overlay.remove();
|
|
6546
7233
|
}
|
|
6547
7234
|
if (!playing || activePlayer !== instance) return;
|
|
6548
7235
|
}
|
|
6549
7236
|
seq.start(0);
|
|
6550
|
-
if (streaming)
|
|
7237
|
+
if (streaming) {
|
|
7238
|
+
ensureVoices().startStream(tracks, seq.getStartTime(), {
|
|
7239
|
+
isAudible: (t) => !mutedTracks.has(Number(t.id))
|
|
7240
|
+
});
|
|
7241
|
+
}
|
|
6551
7242
|
};
|
|
6552
7243
|
const play = () => {
|
|
6553
7244
|
if (playing || trackIndices.length === 0) return;
|
|
@@ -6882,8 +7573,6 @@ var DEFAULT_CDN = {
|
|
|
6882
7573
|
soundFont: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont.mjs",
|
|
6883
7574
|
soundFontDrum: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_drum.mjs",
|
|
6884
7575
|
soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs",
|
|
6885
|
-
parseChord: "https://rpgen3.github.io/piano/mjs/parseChord.mjs",
|
|
6886
|
-
parseChords: "https://rpgen3.github.io/piano/mjs/parseChords.mjs",
|
|
6887
7576
|
midiParser: "https://cdn.jsdelivr.net/npm/midi-parser-js@4.0.4/+esm"
|
|
6888
7577
|
};
|
|
6889
7578
|
var SOUNDFONT_NAME = "FluidR3_GM_sf2_file";
|
|
@@ -6927,24 +7616,6 @@ var createDtmStudio = async (options = {}) => {
|
|
|
6927
7616
|
eng.SoundFont_drum ?? importFrom(cdn.soundFontDrum, "SoundFont_drum"),
|
|
6928
7617
|
eng.SoundFont_list ?? importFrom(cdn.soundFontList, "SoundFont_list")
|
|
6929
7618
|
]);
|
|
6930
|
-
let parseChord = eng.parseChord;
|
|
6931
|
-
let parseChords = eng.parseChords;
|
|
6932
|
-
if (features.chord && (!parseChord || !parseChords)) {
|
|
6933
|
-
try {
|
|
6934
|
-
[parseChord, parseChords] = await Promise.all([
|
|
6935
|
-
parseChord ?? importFrom(
|
|
6936
|
-
cdn.parseChord,
|
|
6937
|
-
"parseChord"
|
|
6938
|
-
),
|
|
6939
|
-
parseChords ?? importFrom(
|
|
6940
|
-
cdn.parseChords,
|
|
6941
|
-
"parseChords"
|
|
6942
|
-
)
|
|
6943
|
-
]);
|
|
6944
|
-
} catch (e) {
|
|
6945
|
-
console.warn("[dtm] \u30B3\u30FC\u30C9\u89E3\u6790\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F", e);
|
|
6946
|
-
}
|
|
6947
|
-
}
|
|
6948
7619
|
let midiParser = null;
|
|
6949
7620
|
let parseMidi;
|
|
6950
7621
|
if (features.midi) {
|
|
@@ -7130,8 +7801,6 @@ var createDtmStudio = async (options = {}) => {
|
|
|
7130
7801
|
onPlayNote: playNote,
|
|
7131
7802
|
onPlayDrum: playDrum,
|
|
7132
7803
|
singingVoices,
|
|
7133
|
-
parseChord,
|
|
7134
|
-
parseChords,
|
|
7135
7804
|
parseMidi,
|
|
7136
7805
|
onToggleRecord,
|
|
7137
7806
|
...dawOverrides
|
|
@@ -7153,15 +7822,32 @@ var createDtmStudio = async (options = {}) => {
|
|
|
7153
7822
|
editorPresetSelects.set(target, select);
|
|
7154
7823
|
select.addEventListener("change", async () => {
|
|
7155
7824
|
if (!select) return;
|
|
7156
|
-
daw.
|
|
7157
|
-
|
|
7825
|
+
const wasPlaying = daw.getPlaybackState() === "playing";
|
|
7826
|
+
if (wasPlaying) {
|
|
7827
|
+
daw.pause();
|
|
7828
|
+
}
|
|
7829
|
+
const overlay = showLoadingOverlay(target);
|
|
7830
|
+
daw.setLoading?.(true);
|
|
7831
|
+
try {
|
|
7832
|
+
daw.setInstrument(select.value);
|
|
7833
|
+
await loadPreset(select.value, trackIds);
|
|
7834
|
+
} finally {
|
|
7835
|
+
overlay.remove();
|
|
7836
|
+
daw.setLoading?.(false);
|
|
7837
|
+
if (wasPlaying) {
|
|
7838
|
+
daw.play();
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7158
7841
|
});
|
|
7159
7842
|
}
|
|
7160
7843
|
const daw = mountDAW(target, base);
|
|
7161
7844
|
mountedEditors.push(daw);
|
|
7162
7845
|
const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
|
|
7163
7846
|
daw.setInstrument(presetKey);
|
|
7164
|
-
|
|
7847
|
+
daw.setLoading?.(true);
|
|
7848
|
+
void loadPreset(presetKey, trackIds).finally(() => {
|
|
7849
|
+
daw.setLoading?.(false);
|
|
7850
|
+
});
|
|
7165
7851
|
const destroy = () => {
|
|
7166
7852
|
daw.destroy();
|
|
7167
7853
|
select?.remove();
|