@onjmin/dtm 0.1.6 → 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/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}`).value];
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).value];
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((err) => {
1751
- console.warn(`[dtm] koe\u97F3\u6E90 "${m}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F`, err);
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);
@@ -1789,42 +2308,33 @@ var createSingingVoices = (ctx, destination, options = {}) => {
1789
2308
  }
1790
2309
  await Promise.all(promises);
1791
2310
  };
1792
- const startStream = (tracks, anchorTime) => {
2311
+ const startStream = (tracks, anchorTime, opts) => {
1793
2312
  const session = ++streamSession;
1794
- const items = [];
1795
- for (const track of tracks) {
2313
+ const runTrack = async (track) => {
1796
2314
  const model = loaded.get(track.model.toLowerCase());
1797
- if (!model) continue;
2315
+ if (!model) return;
2316
+ const items = [];
1798
2317
  forEachSungNote(track, (note, prevVowel) => {
1799
- items.push({
1800
- model,
1801
- note,
1802
- prevVowel,
1803
- volume: track.volume,
1804
- pan: track.pan
1805
- });
2318
+ items.push({ note, prevVowel });
1806
2319
  });
1807
- }
1808
- items.sort((a, b) => a.note.startSec - b.note.startSec);
1809
- void (async () => {
1810
- for (const item of items) {
2320
+ const peak = Math.max(1e-4, track.volume);
2321
+ for (const { note, prevVowel } of items) {
1811
2322
  if (session !== streamSession) return;
1812
- while (item.note.startSec - (ctx.currentTime - anchorTime) > STREAM_LOOKAHEAD_SEC) {
2323
+ while (note.startSec - (ctx.currentTime - anchorTime) > STREAM_LOOKAHEAD_SEC) {
1813
2324
  await new Promise((resolve) => setTimeout(resolve, STREAM_POLL_MS));
1814
2325
  if (session !== streamSession) return;
1815
2326
  }
1816
- const t0 = anchorTime + item.note.startSec;
1817
- const peak = Math.max(1e-4, item.volume);
1818
- const { model, note } = item;
2327
+ if (opts?.isAudible && !opts.isAudible(track)) continue;
2328
+ const t0 = anchorTime + note.startSec;
1819
2329
  if (model.renderToCache && model.scheduleCached) {
1820
2330
  const key = await model.renderToCache(
1821
2331
  note.syllable,
1822
- item.prevVowel,
2332
+ prevVowel,
1823
2333
  note.pitch,
1824
2334
  note.durationSec * 1e3
1825
2335
  );
1826
2336
  if (session !== streamSession) return;
1827
- if (key) model.scheduleCached(key, t0, peak, item.pan);
2337
+ if (key) model.scheduleCached(key, t0, peak, track.pan);
1828
2338
  } else {
1829
2339
  const when = t0 - ctx.currentTime;
1830
2340
  model(note.syllable, {
@@ -1834,12 +2344,13 @@ var createSingingVoices = (ctx, destination, options = {}) => {
1834
2344
  volume: peak,
1835
2345
  when,
1836
2346
  duration: note.durationSec,
1837
- pan: item.pan
2347
+ pan: track.pan
1838
2348
  });
2349
+ await new Promise((resolve) => setTimeout(resolve, 0));
1839
2350
  }
1840
- await new Promise((resolve) => setTimeout(resolve, 0));
1841
2351
  }
1842
- })();
2352
+ };
2353
+ for (const track of tracks) void runTrack(track);
1843
2354
  };
1844
2355
  const stopStream = () => {
1845
2356
  streamSession++;
@@ -3248,8 +3759,10 @@ var parseMML = (mml, options = {}) => {
3248
3759
  numStr += body[j];
3249
3760
  j++;
3250
3761
  }
3251
- if (ch === "t" && trackIndex === 0 && numStr) {
3252
- bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
3762
+ if (ch === "t" && numStr) {
3763
+ if (bpm === null) {
3764
+ bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
3765
+ }
3253
3766
  }
3254
3767
  pushTok("ctrl", currentStep, 0, tokStart);
3255
3768
  } else if (ch === "[") {
@@ -3379,13 +3892,10 @@ var createSequencer = (options) => {
3379
3892
  }
3380
3893
  while (nowIndex < timeline.length) {
3381
3894
  const ev = timeline[nowIndex];
3382
- if (soloId && ev.trackId !== soloId) {
3383
- nowIndex++;
3384
- continue;
3385
- }
3386
3895
  const _when = ev.when - time;
3387
3896
  if (_when > PLAN_TIME) break;
3388
3897
  nowIndex++;
3898
+ if (soloId && ev.trackId !== soloId) continue;
3389
3899
  const velocityVolume = ev.velocity / 127;
3390
3900
  const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
3391
3901
  options.onPlayNote({
@@ -3754,6 +4264,7 @@ var DAW_CSS = `
3754
4264
  }
3755
4265
  .dtm-input--num { width: 64px; text-align: center; font-size: 16px; }
3756
4266
  .dtm-textarea { width: 100%; min-height: 56px; resize: vertical; line-height: 1.7; }
4267
+ .dtm-textarea.dtm-grow { width: 0; }
3757
4268
  .dtm-range { height: var(--dtm-tap); accent-color: var(--dtm-primary); }
3758
4269
 
3759
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 */
@@ -3921,7 +4432,7 @@ var DAW_CSS = `
3921
4432
 
3922
4433
  /* \u2500\u2500\u2500 \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4 \u2500\u2500\u2500 */
3923
4434
  .dtm-overlay {
3924
- position: absolute; inset: 0; z-index: 1000;
4435
+ position: absolute; inset: 0; z-index: 10;
3925
4436
  background: rgba(0,0,0,.92);
3926
4437
  display: flex; align-items: center; justify-content: center;
3927
4438
  flex-direction: column; gap: 14px;
@@ -3970,6 +4481,22 @@ var DAW_CSS = `
3970
4481
  letter-spacing: .15em;
3971
4482
  min-height: 1em;
3972
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
+ }
3973
4500
 
3974
4501
  @keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
3975
4502
  .dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
@@ -4045,6 +4572,14 @@ var DAW_CSS = `
4045
4572
  min-width: 2em;
4046
4573
  margin-left: 4px;
4047
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
+ }
4048
4583
  .dtm-player-dots {
4049
4584
  margin-left: auto;
4050
4585
  display: flex;
@@ -4395,7 +4930,7 @@ var mountDAW = (target, options = {}) => {
4395
4930
  const trackConfigs = options.tracks ?? DEFAULT_TRACKS;
4396
4931
  const drumPatterns = options.drumPatterns ?? DRUM_PATTERNS;
4397
4932
  const showMidi = !!options.parseMidi;
4398
- const showChord = !!(options.parseChord && options.parseChords);
4933
+ const showChord = true;
4399
4934
  const refs = buildUI(target, {
4400
4935
  tracks: trackConfigs,
4401
4936
  drumPatternNames: Object.keys(drumPatterns),
@@ -5080,6 +5615,7 @@ var mountDAW = (target, options = {}) => {
5080
5615
  });
5081
5616
  }
5082
5617
  return {
5618
+ id: trackState?.config.id,
5083
5619
  model: lt.model,
5084
5620
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (masterVolume / 100),
5085
5621
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
@@ -5090,13 +5626,15 @@ var mountDAW = (target, options = {}) => {
5090
5626
  const streaming = !!voices && streamTracks.some((t) => t.notes.length > 0);
5091
5627
  if (streaming && voices) {
5092
5628
  const overlay = showLoadingOverlay(refs.rollContainer);
5629
+ setLoading(true);
5093
5630
  try {
5094
5631
  await voices.loadModels(streamTracks.map((t) => t.model));
5095
5632
  await voices.warm(streamTracks);
5096
- } catch (err) {
5097
- console.warn("[dtm] voice preload failed", err);
5633
+ } catch (err2) {
5634
+ console.warn("[dtm] voice preload failed", err2);
5098
5635
  } finally {
5099
5636
  overlay.remove();
5637
+ setLoading(false);
5100
5638
  }
5101
5639
  }
5102
5640
  if (playbackState !== "paused") {
@@ -5110,7 +5648,9 @@ var mountDAW = (target, options = {}) => {
5110
5648
  playbackState = "playing";
5111
5649
  sequencer.start(fromStep);
5112
5650
  if (streaming && voices) {
5113
- voices.startStream(streamTracks, sequencer.getStartTime());
5651
+ voices.startStream(streamTracks, sequencer.getStartTime(), {
5652
+ isAudible: (t) => !isSolo || t.id === activeTrackId
5653
+ });
5114
5654
  }
5115
5655
  updateTransport();
5116
5656
  };
@@ -5443,21 +5983,28 @@ var mountDAW = (target, options = {}) => {
5443
5983
  barLimit: barLimitBars
5444
5984
  };
5445
5985
  }
5446
- const trackLines = trackStates.map(
5447
- (t, i) => `@${i} ${t.core.getMMLFromNotes(clipNotes(t.core.getNotes()), bpm, t.volume).trim()}`
5448
- );
5449
- const trackLinesMini = trackStates.map(
5450
- (t, i) => `@${i}${t.core.getMMLFromNotes(clipNotes(t.core.getNotes()), bpm, t.volume).trim().replace(/\s+/g, "")}`
5451
- );
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
+ });
5452
5996
  const lyricLines = trackStates.map((t, i) => ({
5453
5997
  i,
5454
- text: t.lyrics.trim(),
5998
+ notes: clipNotes(t.core.getNotes()),
5999
+ text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
5455
6000
  model: t.lyricModel.trim(),
5456
6001
  vol: t.vocalVolume,
5457
6002
  gate: t.vocalGate,
5458
6003
  pan: t.vocalPan,
5459
6004
  oct: t.vocalOctave
5460
- })).filter((x) => x.model.length > 0 && x.text.length > 0).map((x) => {
6005
+ })).filter(
6006
+ (x) => x.model.length > 0 && x.text.length > 0 && x.notes.length > 0
6007
+ ).map((x) => {
5461
6008
  const params = [
5462
6009
  x.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x.vol}`,
5463
6010
  x.gate === 100 ? "" : `q${x.gate}`,
@@ -5473,7 +6020,7 @@ var mountDAW = (target, options = {}) => {
5473
6020
  full,
5474
6021
  minified,
5475
6022
  ignoredCount: 0,
5476
- trackCount: trackStates.length,
6023
+ trackCount: trackLines.length,
5477
6024
  barLimit: barLimitBars
5478
6025
  };
5479
6026
  };
@@ -5593,7 +6140,6 @@ var mountDAW = (target, options = {}) => {
5593
6140
  updateUndoRedo();
5594
6141
  };
5595
6142
  const applyChord = () => {
5596
- if (!options.parseChord || !options.parseChords) return;
5597
6143
  const active = getActive();
5598
6144
  const chordTrack = trackStates.find((t) => t.config.id === "chord");
5599
6145
  if (!chordTrack) return;
@@ -5602,9 +6148,7 @@ var mountDAW = (target, options = {}) => {
5602
6148
  patternType: active.savedChordPattern,
5603
6149
  rootShift: active.savedChordRoot,
5604
6150
  bpm,
5605
- stepsPerBar: renderConfig.stepsPerBar,
5606
- parseChord: options.parseChord,
5607
- parseChords: options.parseChords
6151
+ stepsPerBar: renderConfig.stepsPerBar
5608
6152
  });
5609
6153
  chordTrack.core.clearNotesWithoutHistory();
5610
6154
  chordTrack.core.beginBatch();
@@ -5689,9 +6233,11 @@ var mountDAW = (target, options = {}) => {
5689
6233
  };
5690
6234
  const overlayDuring = (fn) => {
5691
6235
  refs.overlay.hidden = false;
6236
+ setLoading(true);
5692
6237
  setTimeout(() => {
5693
6238
  fn();
5694
6239
  refs.overlay.hidden = true;
6240
+ setLoading(false);
5695
6241
  }, 30);
5696
6242
  };
5697
6243
  const wireEvents = () => {
@@ -5835,6 +6381,7 @@ var mountDAW = (target, options = {}) => {
5835
6381
  const file = refs.midiInput.files?.[0];
5836
6382
  if (!file || !options.parseMidi) return;
5837
6383
  refs.overlay.hidden = false;
6384
+ setLoading(true);
5838
6385
  const buffer = new Uint8Array(await file.arrayBuffer());
5839
6386
  pendingMidi = options.parseMidi(buffer);
5840
6387
  detectedTracks = analyzeMidiTracks(pendingMidi);
@@ -5855,6 +6402,7 @@ var mountDAW = (target, options = {}) => {
5855
6402
  });
5856
6403
  refs.midiTrackSelection.classList.remove("dtm-hidden");
5857
6404
  refs.overlay.hidden = true;
6405
+ setLoading(false);
5858
6406
  });
5859
6407
  refs.midiLoadBtn.addEventListener("click", () => {
5860
6408
  if (!pendingMidi) return;
@@ -5928,6 +6476,9 @@ var mountDAW = (target, options = {}) => {
5928
6476
  resizeObserver.observe(refs.rollContainer);
5929
6477
  document.addEventListener("pointermove", onPointerMove);
5930
6478
  document.addEventListener("pointerup", onPointerUp);
6479
+ const setLoading = (loading) => {
6480
+ refs.topbar.classList.toggle("is-loading", loading);
6481
+ };
5931
6482
  return {
5932
6483
  play,
5933
6484
  pause,
@@ -5965,6 +6516,7 @@ var mountDAW = (target, options = {}) => {
5965
6516
  exportMIDI: exportMIDI2,
5966
6517
  setBpm,
5967
6518
  getPlaybackState: () => playbackState,
6519
+ setLoading,
5968
6520
  destroy: () => {
5969
6521
  sequencer.stop();
5970
6522
  resizeObserver.disconnect();
@@ -6109,6 +6661,117 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6109
6661
  const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
6110
6662
  (a, b) => a - b
6111
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
+ }
6112
6775
  const seqTracks = trackIndices.map((index) => {
6113
6776
  let id = 0;
6114
6777
  const notes = placements.filter((p) => p.trackIndex === index).map((p) => ({
@@ -6309,6 +6972,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6309
6972
  barEl.className = "dtm-player-bar";
6310
6973
  barEl.textContent = "-";
6311
6974
  beatRow.appendChild(barEl);
6975
+ const chordEl = doc.createElement("span");
6976
+ chordEl.className = "dtm-player-chord";
6977
+ chordEl.textContent = "";
6978
+ beatRow.appendChild(chordEl);
6312
6979
  const chips = [];
6313
6980
  const makeChip = (label) => {
6314
6981
  const chip = doc.createElement("span");
@@ -6452,10 +7119,20 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6452
7119
  lane.scrollLeft = Math.max(0, Math.min(next, maxScroll));
6453
7120
  };
6454
7121
  const renderPlayhead = (step) => {
7122
+ const intStep = Math.floor(step);
6455
7123
  const beatIndex = Math.floor(step / STEPS_PER_BEAT3) % 4;
6456
7124
  for (let i = 0; i < 4; i++)
6457
7125
  beatDots[i].classList.toggle("dtm-player-beat-dot--on", i === beatIndex);
6458
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
+ }
6459
7136
  for (const view of laneViews) {
6460
7137
  let active = null;
6461
7138
  for (const t of view.tokens) {
@@ -6469,6 +7146,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6469
7146
  const resetPlayhead = () => {
6470
7147
  for (const d of beatDots) d.classList.remove("dtm-player-beat-dot--on");
6471
7148
  barEl.textContent = "-";
7149
+ chordEl.textContent = "";
6472
7150
  for (const view of laneViews) {
6473
7151
  for (const t of view.tokens) t.el.classList.remove("is-active");
6474
7152
  view.lane.scrollLeft = 0;
@@ -6532,6 +7210,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6532
7210
  });
6533
7211
  }
6534
7212
  return {
7213
+ id: String(index),
6535
7214
  model: lt.model,
6536
7215
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
6537
7216
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
@@ -6547,15 +7226,19 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6547
7226
  try {
6548
7227
  await v.loadModels(tracks.map((t) => t.model));
6549
7228
  await v.warm(tracks);
6550
- } catch (err) {
6551
- console.warn("[dtm] voice preload failed", err);
7229
+ } catch (err2) {
7230
+ console.warn("[dtm] voice preload failed", err2);
6552
7231
  } finally {
6553
7232
  overlay.remove();
6554
7233
  }
6555
7234
  if (!playing || activePlayer !== instance) return;
6556
7235
  }
6557
7236
  seq.start(0);
6558
- if (streaming) ensureVoices().startStream(tracks, seq.getStartTime());
7237
+ if (streaming) {
7238
+ ensureVoices().startStream(tracks, seq.getStartTime(), {
7239
+ isAudible: (t) => !mutedTracks.has(Number(t.id))
7240
+ });
7241
+ }
6559
7242
  };
6560
7243
  const play = () => {
6561
7244
  if (playing || trackIndices.length === 0) return;
@@ -6890,8 +7573,6 @@ var DEFAULT_CDN = {
6890
7573
  soundFont: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont.mjs",
6891
7574
  soundFontDrum: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_drum.mjs",
6892
7575
  soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs",
6893
- parseChord: "https://rpgen3.github.io/piano/mjs/parseChord.mjs",
6894
- parseChords: "https://rpgen3.github.io/piano/mjs/parseChords.mjs",
6895
7576
  midiParser: "https://cdn.jsdelivr.net/npm/midi-parser-js@4.0.4/+esm"
6896
7577
  };
6897
7578
  var SOUNDFONT_NAME = "FluidR3_GM_sf2_file";
@@ -6935,24 +7616,6 @@ var createDtmStudio = async (options = {}) => {
6935
7616
  eng.SoundFont_drum ?? importFrom(cdn.soundFontDrum, "SoundFont_drum"),
6936
7617
  eng.SoundFont_list ?? importFrom(cdn.soundFontList, "SoundFont_list")
6937
7618
  ]);
6938
- let parseChord = eng.parseChord;
6939
- let parseChords = eng.parseChords;
6940
- if (features.chord && (!parseChord || !parseChords)) {
6941
- try {
6942
- [parseChord, parseChords] = await Promise.all([
6943
- parseChord ?? importFrom(
6944
- cdn.parseChord,
6945
- "parseChord"
6946
- ),
6947
- parseChords ?? importFrom(
6948
- cdn.parseChords,
6949
- "parseChords"
6950
- )
6951
- ]);
6952
- } catch (e) {
6953
- console.warn("[dtm] \u30B3\u30FC\u30C9\u89E3\u6790\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F", e);
6954
- }
6955
- }
6956
7619
  let midiParser = null;
6957
7620
  let parseMidi;
6958
7621
  if (features.midi) {
@@ -7138,8 +7801,6 @@ var createDtmStudio = async (options = {}) => {
7138
7801
  onPlayNote: playNote,
7139
7802
  onPlayDrum: playDrum,
7140
7803
  singingVoices,
7141
- parseChord,
7142
- parseChords,
7143
7804
  parseMidi,
7144
7805
  onToggleRecord,
7145
7806
  ...dawOverrides
@@ -7161,15 +7822,32 @@ var createDtmStudio = async (options = {}) => {
7161
7822
  editorPresetSelects.set(target, select);
7162
7823
  select.addEventListener("change", async () => {
7163
7824
  if (!select) return;
7164
- daw.setInstrument(select.value);
7165
- await loadPreset(select.value, trackIds);
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
+ }
7166
7841
  });
7167
7842
  }
7168
7843
  const daw = mountDAW(target, base);
7169
7844
  mountedEditors.push(daw);
7170
7845
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
7171
7846
  daw.setInstrument(presetKey);
7172
- void loadPreset(presetKey, trackIds);
7847
+ daw.setLoading?.(true);
7848
+ void loadPreset(presetKey, trackIds).finally(() => {
7849
+ daw.setLoading?.(false);
7850
+ });
7173
7851
  const destroy = () => {
7174
7852
  daw.destroy();
7175
7853
  select?.remove();