@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.mjs CHANGED
@@ -55,18 +55,534 @@ async function buildNameToKeyMapping() {
55
55
  return nameToKey;
56
56
  }
57
57
 
58
+ // node_modules/.pnpm/@onjmin+chord-parser@1.0.2/node_modules/@onjmin/chord-parser/dist/index.mjs
59
+ var SHARP_NAMES = [
60
+ "C",
61
+ "C#",
62
+ "D",
63
+ "D#",
64
+ "E",
65
+ "F",
66
+ "F#",
67
+ "G",
68
+ "G#",
69
+ "A",
70
+ "A#",
71
+ "B"
72
+ ];
73
+ var FLAT_NAMES = [
74
+ "C",
75
+ "Db",
76
+ "D",
77
+ "Eb",
78
+ "E",
79
+ "F",
80
+ "Gb",
81
+ "G",
82
+ "Ab",
83
+ "A",
84
+ "Bb",
85
+ "B"
86
+ ];
87
+ var toPitchClass = (n) => (n % 12 + 12) % 12;
88
+ var noteName = (pc, flat = false) => (flat ? FLAT_NAMES : SHARP_NAMES)[toPitchClass(pc)];
89
+ var SyntaxErrorWithPos = class extends Error {
90
+ constructor(input, msg) {
91
+ super(
92
+ `SyntaxError: ${msg}
93
+ input.idx: ${input.idx}
94
+ input.str: ${input.str}`
95
+ );
96
+ this.name = "ChordSyntaxError";
97
+ }
98
+ };
99
+ var err = (input, msg) => {
100
+ throw new SyntaxErrorWithPos(input, msg);
101
+ };
102
+ var Input = class _Input {
103
+ static nums = new Set("0123456789");
104
+ str;
105
+ nest;
106
+ idx;
107
+ constructor(str, nest = 0) {
108
+ this.str = str;
109
+ this.nest = nest;
110
+ this.idx = 0;
111
+ }
112
+ get isEOF() {
113
+ return this.str.length <= this.idx;
114
+ }
115
+ get char() {
116
+ return this.str[this.idx];
117
+ }
118
+ /** 先頭の連続する数字を消費して数値で返す(破壊的)。数字が無ければ null。 */
119
+ get num() {
120
+ let str = "";
121
+ while (!this.isEOF) {
122
+ const char = this.char;
123
+ if (!_Input.nums.has(char)) break;
124
+ str += char;
125
+ this.idx++;
126
+ }
127
+ return str.length ? Number(str) : null;
128
+ }
129
+ slice(i) {
130
+ return this.str.slice(this.idx, this.idx + i);
131
+ }
132
+ };
133
+ var Output = class {
134
+ pitch = null;
135
+ chord = null;
136
+ isChord = false;
137
+ pending = null;
138
+ nest = -1;
139
+ get value() {
140
+ const { pitch, chord } = this;
141
+ return new Set(
142
+ [...chord].map((v) => v + pitch)
143
+ );
144
+ }
145
+ set value(chord) {
146
+ const pitch = this.pitch;
147
+ this.chord = new Set([...chord].map((v) => v - pitch));
148
+ }
149
+ };
150
+ var Matcher = class {
151
+ map = /* @__PURE__ */ new Map();
152
+ /** キー長を降順で保持(最長一致のため)。 */
153
+ lengths = [];
154
+ _set(key, value) {
155
+ this.map.set(key, value);
156
+ if (!this.lengths.includes(key.length)) {
157
+ this.lengths.push(key.length);
158
+ this.lengths.sort((a, b) => b - a);
159
+ }
160
+ }
161
+ set(key, value) {
162
+ if (Array.isArray(key)) for (const k of key) this._set(k, value);
163
+ else this._set(key, value);
164
+ }
165
+ parse(input) {
166
+ for (const i of this.lengths) {
167
+ const s = input.slice(i);
168
+ if (this.map.has(s)) {
169
+ input.idx += s.length;
170
+ return this.map.get(s);
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+ };
176
+ var BRACKET_START = 0;
177
+ var BRACKET_END = 1;
178
+ var COMMA = 2;
179
+ var DIVIDE = 3;
180
+ var formulaMatcher = new Matcher();
181
+ formulaMatcher.set("(", BRACKET_START);
182
+ formulaMatcher.set(")", BRACKET_END);
183
+ formulaMatcher.set(",", COMMA);
184
+ formulaMatcher.set(["/", "on"], DIVIDE);
185
+ var parseFormula = (input, output = new Output(), nest = 0) => {
186
+ let start = input.idx;
187
+ const _eval = (idx) => {
188
+ const str = input.str.slice(start, idx);
189
+ if (str.length) parseTerm(new Input(str, nest), output);
190
+ };
191
+ while (true) {
192
+ const { idx } = input;
193
+ if (input.isEOF) {
194
+ if (nest) err(input, `Unclosed ${nest} brackets`);
195
+ _eval(idx);
196
+ return output;
197
+ }
198
+ const res = formulaMatcher.parse(input);
199
+ if (res === null) {
200
+ input.idx++;
201
+ continue;
202
+ }
203
+ const { pending } = output;
204
+ _eval(idx);
205
+ switch (res) {
206
+ case BRACKET_START:
207
+ parseFormula(input, output, nest + 1);
208
+ break;
209
+ case BRACKET_END:
210
+ if (nest - 1 < 0) err(input, "Unable to close brackets");
211
+ return output;
212
+ case COMMA:
213
+ output.pending = pending;
214
+ break;
215
+ case DIVIDE: {
216
+ const o = parseFormula(input, new Output(), nest);
217
+ const v = [...output.value];
218
+ if (o.isChord) {
219
+ output.value = [...o.value].concat(v);
220
+ } else {
221
+ const a = v.sort((x, y) => x - y);
222
+ const pitch = (o.pitch + 3) % 12 - 3;
223
+ if (a[0] < pitch) {
224
+ while (a[0] < pitch) a.push(a.shift() + 12);
225
+ } else {
226
+ while (true) {
227
+ const w = a[a.length - 1] - 12;
228
+ if (w < pitch) break;
229
+ a.pop();
230
+ a.unshift(w);
231
+ }
232
+ }
233
+ a.push(pitch);
234
+ output.value = a;
235
+ }
236
+ break;
237
+ }
238
+ }
239
+ start = input.idx;
240
+ }
241
+ };
242
+ var parseTerm = (input, output) => {
243
+ if (input.isEOF) return output;
244
+ if (output.pitch === null) return parsePitch(input, output);
245
+ if (output.pending === null) return parseFunc(input, output);
246
+ return parsePending(input, output);
247
+ };
248
+ var halfMatcher = new Matcher();
249
+ var halfMatcherStrict = new Matcher();
250
+ for (const m of [halfMatcher, halfMatcherStrict]) {
251
+ m.set(["#", "\u266F"], 1);
252
+ m.set(["b", "\u266D"], -1);
253
+ }
254
+ halfMatcher.set("+", 1);
255
+ halfMatcher.set("-", -1);
256
+ var parseHalf = (input, isPitch = false) => (isPitch ? halfMatcherStrict : halfMatcher).parse(input);
257
+ var idx2pitch = [0, 2, 4, 5, 7, 9, 11];
258
+ for (const i of [...idx2pitch.keys()]) idx2pitch.push(idx2pitch[i] + 12);
259
+ var deg2pitch = (deg) => idx2pitch[deg - 1];
260
+ var pitchMatcher = new Matcher();
261
+ for (const [i, v] of [..."CDEFGAB"].entries())
262
+ pitchMatcher.set(v, idx2pitch[i]);
263
+ var parsePitch = (input, output) => {
264
+ const pitch = pitchMatcher.parse(input);
265
+ if (pitch === null) err(input, "Not found pitch");
266
+ output.pitch = pitch;
267
+ const half = parseHalf(input, true);
268
+ if (half !== null) output.pitch += half;
269
+ return parseBase(input, output);
270
+ };
271
+ var MAJOR = [0, 4, 7];
272
+ var DIM = [0, 3, 6];
273
+ var baseMatcher = new Matcher();
274
+ baseMatcher.set(["m", "min", "Min", "minor", "Minor", "-"], [0, 3, 7]);
275
+ baseMatcher.set(["dim", "\u3007"], DIM);
276
+ baseMatcher.set("+", [0, 4, 8]);
277
+ baseMatcher.set(["\u03A6", "\u03C6", "\xF8"], [0, 3, 6, 10]);
278
+ var parseBase = (input, output) => {
279
+ const isMajMarker = /^maj/i.test(input.str.slice(input.idx));
280
+ const res = isMajMarker ? null : baseMatcher.parse(input);
281
+ if (res !== null) output.isChord = true;
282
+ output.chord = new Set(res || MAJOR);
283
+ if (res === DIM) {
284
+ const { num } = input;
285
+ const chord = output.chord;
286
+ if (num !== null) chord.add(deg2pitch(num) - 2);
287
+ }
288
+ output.nest = input.nest;
289
+ return parseTerm(input, output);
290
+ };
291
+ var add = (chord, n, half) => {
292
+ chord.add(deg2pitch(n) + half);
293
+ };
294
+ var aug = (chord) => {
295
+ chord.delete(deg2pitch(5));
296
+ chord.add(deg2pitch(5) + 1);
297
+ };
298
+ var _7th = (chord, n, _half2, isFlat = false) => {
299
+ if (n === 5) chord.delete(deg2pitch(3));
300
+ else if (n === 6) chord.add(deg2pitch(6));
301
+ else if (n === 69) chord.add(deg2pitch(6)).add(deg2pitch(9));
302
+ else {
303
+ if (n >= 7) chord.add(deg2pitch(7) + (isFlat ? -1 : 0));
304
+ if (n >= 9) chord.add(deg2pitch(9));
305
+ if (n >= 11) chord.add(deg2pitch(11));
306
+ if (n >= 13) chord.add(deg2pitch(13));
307
+ }
308
+ };
309
+ var _half = (chord, n, half) => {
310
+ chord.delete(deg2pitch(n));
311
+ chord.add(deg2pitch(n) + half);
312
+ };
313
+ var funcMatcher = new Matcher();
314
+ funcMatcher.set("add", add);
315
+ funcMatcher.set(["omit", "no"], (chord, n, half) => {
316
+ chord.delete(deg2pitch(n) + half);
317
+ });
318
+ funcMatcher.set("sus", (chord, n, half) => {
319
+ chord.delete(deg2pitch(3));
320
+ chord.add(deg2pitch(n) + half);
321
+ });
322
+ funcMatcher.set(
323
+ ["M", "maj", "Maj", "major", "Major", "\u25B3", "\u0394"],
324
+ _7th
325
+ );
326
+ funcMatcher.set("aug", aug);
327
+ var parseFunc = (input, output) => {
328
+ if (!output.isChord) output.isChord = true;
329
+ const func = funcMatcher.parse(input);
330
+ const chord = output.chord;
331
+ if (func === null) {
332
+ const isAug = input.char === "+";
333
+ const half = parseHalf(input);
334
+ const { num } = input;
335
+ if (num === null) {
336
+ if (isAug) aug(chord);
337
+ else err(input, "Not found number");
338
+ }
339
+ if (half === null) {
340
+ if (input.nest === output.nest) _7th(chord, num, 0, true);
341
+ else add(chord, num, 0);
342
+ } else {
343
+ _half(chord, num, half);
344
+ }
345
+ } else if (func === aug) {
346
+ aug(chord);
347
+ } else {
348
+ output.pending = func;
349
+ }
350
+ return parseTerm(input, output);
351
+ };
352
+ var parsePending = (input, output) => {
353
+ const half = parseHalf(input);
354
+ const { num } = input;
355
+ const { pending, chord } = output;
356
+ if (num === null) err(input, "Not found number");
357
+ pending(
358
+ chord,
359
+ num,
360
+ half === null ? 0 : half
361
+ );
362
+ output.pending = null;
363
+ return parseTerm(input, output);
364
+ };
365
+ var parseChord = (symbol) => {
366
+ const output = parseFormula(new Input(symbol));
367
+ const notes = [...output.value].sort((a, b) => a - b);
368
+ const intervals = [...output.chord].sort((a, b) => a - b);
369
+ const pitchClasses = [...new Set(notes.map(toPitchClass))].sort(
370
+ (a, b) => a - b
371
+ );
372
+ return {
373
+ symbol,
374
+ root: toPitchClass(output.pitch),
375
+ notes,
376
+ pitchClasses,
377
+ intervals
378
+ };
379
+ };
380
+ var QUALITY_SOURCE = [
381
+ "",
382
+ // major
383
+ "m",
384
+ // minor
385
+ "7",
386
+ // dominant 7th
387
+ "M7",
388
+ // major 7th
389
+ "m7",
390
+ // minor 7th
391
+ "dim",
392
+ // diminished triad
393
+ "m7b5",
394
+ // half-diminished
395
+ "aug",
396
+ // augmented triad
397
+ "6",
398
+ // major 6th
399
+ "m6",
400
+ // minor 6th
401
+ "sus4",
402
+ "sus2",
403
+ "mM7",
404
+ // minor major 7th
405
+ "dim7",
406
+ // diminished 7th
407
+ "7sus4",
408
+ "7#5",
409
+ // augmented 7th
410
+ "add9",
411
+ "madd9",
412
+ "9",
413
+ "M9",
414
+ "m9",
415
+ "69",
416
+ "m69",
417
+ "5"
418
+ // power chord
419
+ ];
420
+ var QUALITIES = QUALITY_SOURCE.map(
421
+ (quality, priority) => ({
422
+ quality,
423
+ pitchClasses: parseChord(`C${quality}`).pitchClasses,
424
+ priority
425
+ })
426
+ );
427
+ var QUALITY_BY_PCSET = (() => {
428
+ const map = /* @__PURE__ */ new Map();
429
+ for (const def of QUALITIES) {
430
+ const key = def.pitchClasses.join(",");
431
+ if (!map.has(key)) map.set(key, def);
432
+ }
433
+ return map;
434
+ })();
435
+ var detectChord = (notes, options = {}) => {
436
+ if (!notes.length) return [];
437
+ const { flat = false } = options;
438
+ const pcs = [...new Set(notes.map(toPitchClass))].sort((a, b) => a - b);
439
+ const bass = toPitchClass(
440
+ options.bass ?? notes.reduce((m, v) => Math.min(m, v), notes[0])
441
+ );
442
+ const scored = [];
443
+ for (const root of pcs) {
444
+ const relSet = new Set(pcs.map((pc) => toPitchClass(pc - root)));
445
+ for (const def of QUALITY_BY_PCSET.values()) {
446
+ let matchCount = 0;
447
+ let hasRoot = false;
448
+ for (const pc of def.pitchClasses) {
449
+ if (relSet.has(pc)) {
450
+ matchCount++;
451
+ if (pc === 0) hasRoot = true;
452
+ }
453
+ }
454
+ if (!hasRoot) continue;
455
+ if (matchCount < Math.min(2, def.pitchClasses.length)) continue;
456
+ const missingCount = def.pitchClasses.length - matchCount;
457
+ let extraCount = 0;
458
+ const defSet = new Set(def.pitchClasses);
459
+ for (const pc of relSet) {
460
+ if (!defSet.has(pc)) {
461
+ extraCount++;
462
+ }
463
+ }
464
+ const score = matchCount * 2 - missingCount * 1 - extraCount * 0.5;
465
+ const rootSymbol = noteName(root, flat) + def.quality;
466
+ const inversion = root !== bass;
467
+ scored.push({
468
+ score,
469
+ priority: def.priority,
470
+ candidate: {
471
+ symbol: inversion ? `${rootSymbol}/${noteName(bass, flat)}` : rootSymbol,
472
+ rootSymbol,
473
+ root,
474
+ quality: def.quality,
475
+ bass,
476
+ inversion
477
+ }
478
+ });
479
+ }
480
+ }
481
+ scored.sort((a, b) => {
482
+ if (Math.abs(a.score - b.score) > 1e-3) return b.score - a.score;
483
+ if (a.candidate.inversion !== b.candidate.inversion)
484
+ return a.candidate.inversion ? 1 : -1;
485
+ if (a.priority !== b.priority) return a.priority - b.priority;
486
+ return a.candidate.root - b.candidate.root;
487
+ });
488
+ return scored.map((s) => s.candidate);
489
+ };
490
+ var toneRoleWeight = (rel) => {
491
+ if (rel === 0) return 1.3;
492
+ if (rel === 3 || rel === 4) return 1.2;
493
+ if (rel === 10 || rel === 11) return 0.95;
494
+ if (rel === 6 || rel === 7 || rel === 8) return 0.7;
495
+ return 0.85;
496
+ };
497
+ var CHORD_TEMPLATES = (() => {
498
+ const templates = [];
499
+ for (let root = 0; root < 12; root++) {
500
+ for (const def of QUALITIES) {
501
+ const pcs = /* @__PURE__ */ new Set();
502
+ const weights = new Array(12).fill(0);
503
+ const rel = /* @__PURE__ */ new Set();
504
+ for (const relPc of def.pitchClasses) {
505
+ rel.add(relPc);
506
+ const pc = toPitchClass(relPc + root);
507
+ pcs.add(pc);
508
+ weights[pc] = toneRoleWeight(relPc);
509
+ }
510
+ templates.push({
511
+ root,
512
+ quality: def.quality,
513
+ priority: def.priority,
514
+ pcs,
515
+ weights,
516
+ rel
517
+ });
518
+ }
519
+ }
520
+ return templates;
521
+ })();
522
+ var toHan = (str) => str.replace(/[!-~]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 65248)).replace(/ /g, " ");
523
+ var parseChords = (str, bpm = 120) => {
524
+ const output = [];
525
+ const secBar = 60 / bpm * 4;
526
+ const frontChars = new Set("ABCDEFG_=%N");
527
+ let idx = 0;
528
+ let last = null;
529
+ for (const line of toHan(str).split("\n").map((v) => v.trim())) {
530
+ if (!line.length || /^#/.test(line)) continue;
531
+ for (const str2 of line.split(/[|ll→]/)) {
532
+ if (!str2.length) continue;
533
+ const when = idx++ * secBar;
534
+ const a = [];
535
+ for (let i = 0; i < str2.length; i++) {
536
+ const char = str2[i];
537
+ const prev = str2[i - 1];
538
+ const prev2 = str2.slice(i - 2, i);
539
+ if (!frontChars.has(char)) continue;
540
+ if (prev === "/" || prev2 === "on") continue;
541
+ if (prev2 === "N." && char === "C") continue;
542
+ a.push(i);
543
+ }
544
+ if (!a.length) continue;
545
+ const divide = 2 ** Math.ceil(Math.log2(a.length));
546
+ const unitTime = secBar / divide;
547
+ for (const [i, v] of a.entries()) {
548
+ const s = str2.slice(v, i === a.length - 1 ? str2.length : a[i + 1]).replace(/\s+/g, "");
549
+ const c = s[0];
550
+ if (c === "_" || c === "N") {
551
+ last = null;
552
+ continue;
553
+ }
554
+ if (c === "=") {
555
+ if (last) last.duration += unitTime;
556
+ continue;
557
+ }
558
+ const _when = when + i * unitTime;
559
+ if (c === "%") {
560
+ if (last === null) continue;
561
+ const base = last;
562
+ last = { ...base, when: _when, duration: unitTime };
563
+ } else {
564
+ const key = s.slice(0, s[1] === "#" ? 2 : 1);
565
+ const chord = s.slice(key.length).replace(/[\s・]/g, "");
566
+ last = {
567
+ key,
568
+ chord,
569
+ when: _when,
570
+ duration: unitTime
571
+ };
572
+ }
573
+ output.push(last);
574
+ }
575
+ if (last !== null && divide > a.length)
576
+ last.duration += unitTime * (divide - a.length);
577
+ }
578
+ }
579
+ return output;
580
+ };
581
+
58
582
  // src/chords.ts
59
583
  var C3 = 48;
60
584
  var buildChordPlacements = (options) => {
61
- const {
62
- chordStr,
63
- patternType,
64
- rootShift,
65
- bpm,
66
- stepsPerBar,
67
- parseChord,
68
- parseChords
69
- } = options;
585
+ const { chordStr, patternType, rootShift, bpm, stepsPerBar } = options;
70
586
  const placements = [];
71
587
  if (!chordStr.trim()) return placements;
72
588
  const offset = rootShift;
@@ -96,7 +612,7 @@ var buildChordPlacements = (options) => {
96
612
  for (const chord of group) {
97
613
  let notes;
98
614
  try {
99
- notes = [...parseChord(`${chord.key}${chord.chord}`).value];
615
+ notes = [...parseChord(`${chord.key}${chord.chord}`).notes];
100
616
  } catch {
101
617
  continue;
102
618
  }
@@ -183,7 +699,7 @@ var buildChordPlacements = (options) => {
183
699
  chordNames.forEach((chordName, barIndex) => {
184
700
  let notes;
185
701
  try {
186
- notes = [...parseChord(chordName).value];
702
+ notes = [...parseChord(chordName).notes];
187
703
  } catch {
188
704
  return;
189
705
  }
@@ -257,6 +773,7 @@ var buildUI = (target, options) => {
257
773
  <button class="dtm-play" data-dtm="play" disabled>${icon("play")}<span>\u8A66\u8074</span></button>
258
774
  <button class="dtm-iconbtn dtm-rec" data-dtm="rec" title="\u9332\u97F3">${icon("record")}</button>
259
775
  <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
776
+ <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
260
777
  <span class="dtm-grow"></span>
261
778
  <span class="dtm-label">BPM</span>
262
779
  <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
@@ -349,10 +866,10 @@ var buildUI = (target, options) => {
349
866
  <div class="dtm-row dtm-hidden" data-dtm="midi-track-selection"></div>
350
867
  <div class="dtm-row">
351
868
  <span class="dtm-label">MML</span>
352
- <textarea class="dtm-textarea" data-dtm="mml-input" placeholder="MML\u3092\u5165\u529B"></textarea>
869
+ <textarea class="dtm-textarea dtm-grow" data-dtm="mml-input" placeholder="MML\u3092\u5165\u529B"></textarea>
870
+ <button class="dtm-btn dtm-btn--primary" data-dtm="mml-load">\u8AAD\u8FBC</button>
353
871
  </div>
354
872
  <div class="dtm-row">
355
- <button class="dtm-btn dtm-btn--primary" data-dtm="mml-load">MML\u8AAD\u8FBC</button>
356
873
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
357
874
  <select class="dtm-select" data-dtm="shift-select">
358
875
  <option value="-96">-2\u5206</option>
@@ -426,6 +943,8 @@ var buildUI = (target, options) => {
426
943
  const sel = (name) => q(root, `[data-dtm="${name}"]`);
427
944
  return {
428
945
  root,
946
+ topbar: sel("transport"),
947
+ topbarLoading: sel("topbar-loading"),
429
948
  playBtn: sel("play"),
430
949
  recBtn: sel("rec"),
431
950
  soloCheckbox: sel("solo"),
@@ -1643,8 +2162,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
1643
2162
  ))().then((v) => {
1644
2163
  loaded.set(m, v);
1645
2164
  return v;
1646
- }).catch((err) => {
1647
- console.warn(`[dtm] koe\u97F3\u6E90 "${m}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F`, err);
2165
+ }).catch((err2) => {
2166
+ console.warn(`[dtm] koe\u97F3\u6E90 "${m}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F`, err2);
1648
2167
  return null;
1649
2168
  });
1650
2169
  loading.set(m, p);
@@ -1685,42 +2204,33 @@ var createSingingVoices = (ctx, destination, options = {}) => {
1685
2204
  }
1686
2205
  await Promise.all(promises);
1687
2206
  };
1688
- const startStream = (tracks, anchorTime) => {
2207
+ const startStream = (tracks, anchorTime, opts) => {
1689
2208
  const session = ++streamSession;
1690
- const items = [];
1691
- for (const track of tracks) {
2209
+ const runTrack = async (track) => {
1692
2210
  const model = loaded.get(track.model.toLowerCase());
1693
- if (!model) continue;
2211
+ if (!model) return;
2212
+ const items = [];
1694
2213
  forEachSungNote(track, (note, prevVowel) => {
1695
- items.push({
1696
- model,
1697
- note,
1698
- prevVowel,
1699
- volume: track.volume,
1700
- pan: track.pan
1701
- });
2214
+ items.push({ note, prevVowel });
1702
2215
  });
1703
- }
1704
- items.sort((a, b) => a.note.startSec - b.note.startSec);
1705
- void (async () => {
1706
- for (const item of items) {
2216
+ const peak = Math.max(1e-4, track.volume);
2217
+ for (const { note, prevVowel } of items) {
1707
2218
  if (session !== streamSession) return;
1708
- while (item.note.startSec - (ctx.currentTime - anchorTime) > STREAM_LOOKAHEAD_SEC) {
2219
+ while (note.startSec - (ctx.currentTime - anchorTime) > STREAM_LOOKAHEAD_SEC) {
1709
2220
  await new Promise((resolve) => setTimeout(resolve, STREAM_POLL_MS));
1710
2221
  if (session !== streamSession) return;
1711
2222
  }
1712
- const t0 = anchorTime + item.note.startSec;
1713
- const peak = Math.max(1e-4, item.volume);
1714
- const { model, note } = item;
2223
+ if (opts?.isAudible && !opts.isAudible(track)) continue;
2224
+ const t0 = anchorTime + note.startSec;
1715
2225
  if (model.renderToCache && model.scheduleCached) {
1716
2226
  const key = await model.renderToCache(
1717
2227
  note.syllable,
1718
- item.prevVowel,
2228
+ prevVowel,
1719
2229
  note.pitch,
1720
2230
  note.durationSec * 1e3
1721
2231
  );
1722
2232
  if (session !== streamSession) return;
1723
- if (key) model.scheduleCached(key, t0, peak, item.pan);
2233
+ if (key) model.scheduleCached(key, t0, peak, track.pan);
1724
2234
  } else {
1725
2235
  const when = t0 - ctx.currentTime;
1726
2236
  model(note.syllable, {
@@ -1730,12 +2240,13 @@ var createSingingVoices = (ctx, destination, options = {}) => {
1730
2240
  volume: peak,
1731
2241
  when,
1732
2242
  duration: note.durationSec,
1733
- pan: item.pan
2243
+ pan: track.pan
1734
2244
  });
2245
+ await new Promise((resolve) => setTimeout(resolve, 0));
1735
2246
  }
1736
- await new Promise((resolve) => setTimeout(resolve, 0));
1737
2247
  }
1738
- })();
2248
+ };
2249
+ for (const track of tracks) void runTrack(track);
1739
2250
  };
1740
2251
  const stopStream = () => {
1741
2252
  streamSession++;
@@ -3144,8 +3655,10 @@ var parseMML = (mml, options = {}) => {
3144
3655
  numStr += body[j];
3145
3656
  j++;
3146
3657
  }
3147
- if (ch === "t" && trackIndex === 0 && numStr) {
3148
- bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
3658
+ if (ch === "t" && numStr) {
3659
+ if (bpm === null) {
3660
+ bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
3661
+ }
3149
3662
  }
3150
3663
  pushTok("ctrl", currentStep, 0, tokStart);
3151
3664
  } else if (ch === "[") {
@@ -3275,13 +3788,10 @@ var createSequencer = (options) => {
3275
3788
  }
3276
3789
  while (nowIndex < timeline.length) {
3277
3790
  const ev = timeline[nowIndex];
3278
- if (soloId && ev.trackId !== soloId) {
3279
- nowIndex++;
3280
- continue;
3281
- }
3282
3791
  const _when = ev.when - time;
3283
3792
  if (_when > PLAN_TIME) break;
3284
3793
  nowIndex++;
3794
+ if (soloId && ev.trackId !== soloId) continue;
3285
3795
  const velocityVolume = ev.velocity / 127;
3286
3796
  const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
3287
3797
  options.onPlayNote({
@@ -3650,6 +4160,7 @@ var DAW_CSS = `
3650
4160
  }
3651
4161
  .dtm-input--num { width: 64px; text-align: center; font-size: 16px; }
3652
4162
  .dtm-textarea { width: 100%; min-height: 56px; resize: vertical; line-height: 1.7; }
4163
+ .dtm-textarea.dtm-grow { width: 0; }
3653
4164
  .dtm-range { height: var(--dtm-tap); accent-color: var(--dtm-primary); }
3654
4165
 
3655
4166
  /* \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 */
@@ -3817,7 +4328,7 @@ var DAW_CSS = `
3817
4328
 
3818
4329
  /* \u2500\u2500\u2500 \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4 \u2500\u2500\u2500 */
3819
4330
  .dtm-overlay {
3820
- position: absolute; inset: 0; z-index: 1000;
4331
+ position: absolute; inset: 0; z-index: 10;
3821
4332
  background: rgba(0,0,0,.92);
3822
4333
  display: flex; align-items: center; justify-content: center;
3823
4334
  flex-direction: column; gap: 14px;
@@ -3866,6 +4377,22 @@ var DAW_CSS = `
3866
4377
  letter-spacing: .15em;
3867
4378
  min-height: 1em;
3868
4379
  }
4380
+ .dtm-topbar-loading {
4381
+ display: none;
4382
+ font-family: var(--dtm-font);
4383
+ font-size: 11px;
4384
+ color: var(--dtm-primary);
4385
+ margin-left: 12px;
4386
+ letter-spacing: .15em;
4387
+ align-self: center;
4388
+ }
4389
+ .dtm-topbar.is-loading .dtm-topbar-loading {
4390
+ display: inline-block;
4391
+ }
4392
+ .dtm-topbar.is-loading {
4393
+ pointer-events: none;
4394
+ opacity: 0.7;
4395
+ }
3869
4396
 
3870
4397
  @keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
3871
4398
  .dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
@@ -3941,6 +4468,14 @@ var DAW_CSS = `
3941
4468
  min-width: 2em;
3942
4469
  margin-left: 4px;
3943
4470
  }
4471
+ .dtm-player-chord {
4472
+ font-family: 'k8x12', monospace;
4473
+ font-size: 11px;
4474
+ color: var(--dtm-accent);
4475
+ min-width: 4em;
4476
+ margin-left: 8px;
4477
+ font-weight: bold;
4478
+ }
3944
4479
  .dtm-player-dots {
3945
4480
  margin-left: auto;
3946
4481
  display: flex;
@@ -4291,7 +4826,7 @@ var mountDAW = (target, options = {}) => {
4291
4826
  const trackConfigs = options.tracks ?? DEFAULT_TRACKS;
4292
4827
  const drumPatterns = options.drumPatterns ?? DRUM_PATTERNS;
4293
4828
  const showMidi = !!options.parseMidi;
4294
- const showChord = !!(options.parseChord && options.parseChords);
4829
+ const showChord = true;
4295
4830
  const refs = buildUI(target, {
4296
4831
  tracks: trackConfigs,
4297
4832
  drumPatternNames: Object.keys(drumPatterns),
@@ -4976,6 +5511,7 @@ var mountDAW = (target, options = {}) => {
4976
5511
  });
4977
5512
  }
4978
5513
  return {
5514
+ id: trackState?.config.id,
4979
5515
  model: lt.model,
4980
5516
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (masterVolume / 100),
4981
5517
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
@@ -4986,13 +5522,15 @@ var mountDAW = (target, options = {}) => {
4986
5522
  const streaming = !!voices && streamTracks.some((t) => t.notes.length > 0);
4987
5523
  if (streaming && voices) {
4988
5524
  const overlay = showLoadingOverlay(refs.rollContainer);
5525
+ setLoading(true);
4989
5526
  try {
4990
5527
  await voices.loadModels(streamTracks.map((t) => t.model));
4991
5528
  await voices.warm(streamTracks);
4992
- } catch (err) {
4993
- console.warn("[dtm] voice preload failed", err);
5529
+ } catch (err2) {
5530
+ console.warn("[dtm] voice preload failed", err2);
4994
5531
  } finally {
4995
5532
  overlay.remove();
5533
+ setLoading(false);
4996
5534
  }
4997
5535
  }
4998
5536
  if (playbackState !== "paused") {
@@ -5006,7 +5544,9 @@ var mountDAW = (target, options = {}) => {
5006
5544
  playbackState = "playing";
5007
5545
  sequencer.start(fromStep);
5008
5546
  if (streaming && voices) {
5009
- voices.startStream(streamTracks, sequencer.getStartTime());
5547
+ voices.startStream(streamTracks, sequencer.getStartTime(), {
5548
+ isAudible: (t) => !isSolo || t.id === activeTrackId
5549
+ });
5010
5550
  }
5011
5551
  updateTransport();
5012
5552
  };
@@ -5339,21 +5879,28 @@ var mountDAW = (target, options = {}) => {
5339
5879
  barLimit: barLimitBars
5340
5880
  };
5341
5881
  }
5342
- const trackLines = trackStates.map(
5343
- (t, i) => `@${i} ${t.core.getMMLFromNotes(clipNotes(t.core.getNotes()), bpm, t.volume).trim()}`
5344
- );
5345
- const trackLinesMini = trackStates.map(
5346
- (t, i) => `@${i}${t.core.getMMLFromNotes(clipNotes(t.core.getNotes()), bpm, t.volume).trim().replace(/\s+/g, "")}`
5347
- );
5882
+ const trackLines = [];
5883
+ const trackLinesMini = [];
5884
+ trackStates.forEach((t, i) => {
5885
+ const notes = clipNotes(t.core.getNotes());
5886
+ if (notes.length > 0) {
5887
+ const mml = t.core.getMMLFromNotes(notes, bpm, t.volume).trim();
5888
+ trackLines.push(`@${i} ${mml}`);
5889
+ trackLinesMini.push(`@${i}${mml.replace(/\s+/g, "")}`);
5890
+ }
5891
+ });
5348
5892
  const lyricLines = trackStates.map((t, i) => ({
5349
5893
  i,
5350
- text: t.lyrics.trim(),
5894
+ notes: clipNotes(t.core.getNotes()),
5895
+ text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
5351
5896
  model: t.lyricModel.trim(),
5352
5897
  vol: t.vocalVolume,
5353
5898
  gate: t.vocalGate,
5354
5899
  pan: t.vocalPan,
5355
5900
  oct: t.vocalOctave
5356
- })).filter((x) => x.model.length > 0 && x.text.length > 0).map((x) => {
5901
+ })).filter(
5902
+ (x) => x.model.length > 0 && x.text.length > 0 && x.notes.length > 0
5903
+ ).map((x) => {
5357
5904
  const params = [
5358
5905
  x.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x.vol}`,
5359
5906
  x.gate === 100 ? "" : `q${x.gate}`,
@@ -5369,7 +5916,7 @@ var mountDAW = (target, options = {}) => {
5369
5916
  full,
5370
5917
  minified,
5371
5918
  ignoredCount: 0,
5372
- trackCount: trackStates.length,
5919
+ trackCount: trackLines.length,
5373
5920
  barLimit: barLimitBars
5374
5921
  };
5375
5922
  };
@@ -5489,7 +6036,6 @@ var mountDAW = (target, options = {}) => {
5489
6036
  updateUndoRedo();
5490
6037
  };
5491
6038
  const applyChord = () => {
5492
- if (!options.parseChord || !options.parseChords) return;
5493
6039
  const active = getActive();
5494
6040
  const chordTrack = trackStates.find((t) => t.config.id === "chord");
5495
6041
  if (!chordTrack) return;
@@ -5498,9 +6044,7 @@ var mountDAW = (target, options = {}) => {
5498
6044
  patternType: active.savedChordPattern,
5499
6045
  rootShift: active.savedChordRoot,
5500
6046
  bpm,
5501
- stepsPerBar: renderConfig.stepsPerBar,
5502
- parseChord: options.parseChord,
5503
- parseChords: options.parseChords
6047
+ stepsPerBar: renderConfig.stepsPerBar
5504
6048
  });
5505
6049
  chordTrack.core.clearNotesWithoutHistory();
5506
6050
  chordTrack.core.beginBatch();
@@ -5585,9 +6129,11 @@ var mountDAW = (target, options = {}) => {
5585
6129
  };
5586
6130
  const overlayDuring = (fn) => {
5587
6131
  refs.overlay.hidden = false;
6132
+ setLoading(true);
5588
6133
  setTimeout(() => {
5589
6134
  fn();
5590
6135
  refs.overlay.hidden = true;
6136
+ setLoading(false);
5591
6137
  }, 30);
5592
6138
  };
5593
6139
  const wireEvents = () => {
@@ -5731,6 +6277,7 @@ var mountDAW = (target, options = {}) => {
5731
6277
  const file = refs.midiInput.files?.[0];
5732
6278
  if (!file || !options.parseMidi) return;
5733
6279
  refs.overlay.hidden = false;
6280
+ setLoading(true);
5734
6281
  const buffer = new Uint8Array(await file.arrayBuffer());
5735
6282
  pendingMidi = options.parseMidi(buffer);
5736
6283
  detectedTracks = analyzeMidiTracks(pendingMidi);
@@ -5751,6 +6298,7 @@ var mountDAW = (target, options = {}) => {
5751
6298
  });
5752
6299
  refs.midiTrackSelection.classList.remove("dtm-hidden");
5753
6300
  refs.overlay.hidden = true;
6301
+ setLoading(false);
5754
6302
  });
5755
6303
  refs.midiLoadBtn.addEventListener("click", () => {
5756
6304
  if (!pendingMidi) return;
@@ -5824,6 +6372,9 @@ var mountDAW = (target, options = {}) => {
5824
6372
  resizeObserver.observe(refs.rollContainer);
5825
6373
  document.addEventListener("pointermove", onPointerMove);
5826
6374
  document.addEventListener("pointerup", onPointerUp);
6375
+ const setLoading = (loading) => {
6376
+ refs.topbar.classList.toggle("is-loading", loading);
6377
+ };
5827
6378
  return {
5828
6379
  play,
5829
6380
  pause,
@@ -5861,6 +6412,7 @@ var mountDAW = (target, options = {}) => {
5861
6412
  exportMIDI: exportMIDI2,
5862
6413
  setBpm,
5863
6414
  getPlaybackState: () => playbackState,
6415
+ setLoading,
5864
6416
  destroy: () => {
5865
6417
  sequencer.stop();
5866
6418
  resizeObserver.disconnect();
@@ -6005,6 +6557,117 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6005
6557
  const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
6006
6558
  (a, b) => a - b
6007
6559
  );
6560
+ const trackStats = trackIndices.map((index) => {
6561
+ const trackPlacements = placements.filter((p) => p.trackIndex === index);
6562
+ if (trackPlacements.length === 0) {
6563
+ return { index, isChords: false, avgPitch: 0, noteCount: 0 };
6564
+ }
6565
+ const sumPitch = trackPlacements.reduce((sum, p) => sum + p.pitch, 0);
6566
+ const avgPitch = sumPitch / trackPlacements.length;
6567
+ const stepCounts = /* @__PURE__ */ new Map();
6568
+ for (const p of trackPlacements) {
6569
+ for (let s = p.startStep; s < p.startStep + p.durationSteps; s++) {
6570
+ stepCounts.set(s, (stepCounts.get(s) ?? 0) + 1);
6571
+ }
6572
+ }
6573
+ const polyphonicSteps = Array.from(stepCounts.values()).filter(
6574
+ (c) => c >= 2
6575
+ ).length;
6576
+ const isChords = polyphonicSteps > 0;
6577
+ return {
6578
+ index,
6579
+ isChords,
6580
+ avgPitch,
6581
+ noteCount: trackPlacements.length
6582
+ };
6583
+ });
6584
+ const chordTrackIndices = trackStats.filter((s) => s.isChords).map((s) => s.index);
6585
+ const nonChordTracks = trackStats.filter(
6586
+ (s) => !s.isChords && s.noteCount > 0
6587
+ );
6588
+ let bassTrackIndex = null;
6589
+ if (nonChordTracks.length > 0) {
6590
+ nonChordTracks.sort((a, b) => a.avgPitch - b.avgPitch);
6591
+ bassTrackIndex = nonChordTracks[0].index;
6592
+ }
6593
+ const priorityTrackIndices = /* @__PURE__ */ new Set();
6594
+ for (const idx of chordTrackIndices) {
6595
+ priorityTrackIndices.add(idx);
6596
+ }
6597
+ if (bassTrackIndex !== null) {
6598
+ priorityTrackIndices.add(bassTrackIndex);
6599
+ }
6600
+ const hasPriorityTracksInMml = priorityTrackIndices.size > 0;
6601
+ const maxStep = placements.reduce(
6602
+ (max, p) => Math.max(max, p.startStep + p.durationSteps),
6603
+ 0
6604
+ );
6605
+ const priorityPitches = Array.from(
6606
+ { length: maxStep + 1 },
6607
+ () => /* @__PURE__ */ new Set()
6608
+ );
6609
+ const allPitches = Array.from(
6610
+ { length: maxStep + 1 },
6611
+ () => /* @__PURE__ */ new Set()
6612
+ );
6613
+ for (const p of placements) {
6614
+ for (let s = p.startStep; s < p.startStep + p.durationSteps; s++) {
6615
+ if (s >= 0 && s <= maxStep) {
6616
+ allPitches[s].add(p.pitch);
6617
+ if (priorityTrackIndices.has(p.trackIndex)) {
6618
+ priorityPitches[s].add(p.pitch);
6619
+ }
6620
+ }
6621
+ }
6622
+ }
6623
+ const stepChords = [];
6624
+ const chordCache = /* @__PURE__ */ new Map();
6625
+ let lastChord = "";
6626
+ let hasPriorityStarted = false;
6627
+ const GRID_SIZE = 48;
6628
+ const MIN_DURATION = 12;
6629
+ for (let g = 0; g <= Math.ceil(maxStep / GRID_SIZE); g++) {
6630
+ const startS = g * GRID_SIZE;
6631
+ const endS = Math.min(maxStep, (g + 1) * GRID_SIZE - 1);
6632
+ if (startS > maxStep) break;
6633
+ for (let s = startS; s <= endS; s++) {
6634
+ const priorityPitchesAtStep = Array.from(priorityPitches[s]);
6635
+ if (priorityPitchesAtStep.length > 0) {
6636
+ hasPriorityStarted = true;
6637
+ }
6638
+ }
6639
+ const pitchDurations = /* @__PURE__ */ new Map();
6640
+ for (let s = startS; s <= endS; s++) {
6641
+ const usePriority = hasPriorityStarted && hasPriorityTracksInMml;
6642
+ const pitches = usePriority ? Array.from(priorityPitches[s]) : Array.from(allPitches[s]);
6643
+ for (const p of pitches) {
6644
+ pitchDurations.set(p, (pitchDurations.get(p) ?? 0) + 1);
6645
+ }
6646
+ }
6647
+ let activePitches = Array.from(pitchDurations.entries()).filter(([_, dur]) => dur >= MIN_DURATION).map(([p, _]) => p);
6648
+ if (activePitches.length === 0 && pitchDurations.size > 0) {
6649
+ const maxDur = Math.max(...pitchDurations.values());
6650
+ activePitches = Array.from(pitchDurations.entries()).filter(([_, dur]) => dur === maxDur).map(([p, _]) => p);
6651
+ }
6652
+ let gridChord = lastChord;
6653
+ if (activePitches.length > 0) {
6654
+ const sortedPitches = activePitches.sort((a, b) => a - b);
6655
+ const cacheKey = sortedPitches.join(",");
6656
+ if (chordCache.has(cacheKey)) {
6657
+ const chordName = chordCache.get(cacheKey);
6658
+ if (chordName) gridChord = chordName;
6659
+ } else {
6660
+ const candidates = detectChord(sortedPitches);
6661
+ const chordName = candidates[0]?.symbol ?? "";
6662
+ if (chordName) gridChord = chordName;
6663
+ chordCache.set(cacheKey, chordName);
6664
+ }
6665
+ }
6666
+ for (let s = startS; s <= endS; s++) {
6667
+ stepChords[s] = gridChord;
6668
+ }
6669
+ lastChord = gridChord;
6670
+ }
6008
6671
  const seqTracks = trackIndices.map((index) => {
6009
6672
  let id = 0;
6010
6673
  const notes = placements.filter((p) => p.trackIndex === index).map((p) => ({
@@ -6205,6 +6868,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6205
6868
  barEl.className = "dtm-player-bar";
6206
6869
  barEl.textContent = "-";
6207
6870
  beatRow.appendChild(barEl);
6871
+ const chordEl = doc.createElement("span");
6872
+ chordEl.className = "dtm-player-chord";
6873
+ chordEl.textContent = "";
6874
+ beatRow.appendChild(chordEl);
6208
6875
  const chips = [];
6209
6876
  const makeChip = (label) => {
6210
6877
  const chip = doc.createElement("span");
@@ -6348,10 +7015,20 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6348
7015
  lane.scrollLeft = Math.max(0, Math.min(next, maxScroll));
6349
7016
  };
6350
7017
  const renderPlayhead = (step) => {
7018
+ const intStep = Math.floor(step);
6351
7019
  const beatIndex = Math.floor(step / STEPS_PER_BEAT3) % 4;
6352
7020
  for (let i = 0; i < 4; i++)
6353
7021
  beatDots[i].classList.toggle("dtm-player-beat-dot--on", i === beatIndex);
6354
7022
  barEl.textContent = String(Math.floor(step / STEPS_PER_BAR) + 1);
7023
+ const chordName = stepChords[intStep] ?? "";
7024
+ if (chordEl.textContent !== chordName) {
7025
+ chordEl.textContent = chordName;
7026
+ if (chordName) {
7027
+ console.log(
7028
+ `[dtm-player-chord] Active Chord: ${chordName} (step: ${intStep})`
7029
+ );
7030
+ }
7031
+ }
6355
7032
  for (const view of laneViews) {
6356
7033
  let active = null;
6357
7034
  for (const t of view.tokens) {
@@ -6365,6 +7042,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6365
7042
  const resetPlayhead = () => {
6366
7043
  for (const d of beatDots) d.classList.remove("dtm-player-beat-dot--on");
6367
7044
  barEl.textContent = "-";
7045
+ chordEl.textContent = "";
6368
7046
  for (const view of laneViews) {
6369
7047
  for (const t of view.tokens) t.el.classList.remove("is-active");
6370
7048
  view.lane.scrollLeft = 0;
@@ -6428,6 +7106,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6428
7106
  });
6429
7107
  }
6430
7108
  return {
7109
+ id: String(index),
6431
7110
  model: lt.model,
6432
7111
  volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
6433
7112
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
@@ -6443,15 +7122,19 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6443
7122
  try {
6444
7123
  await v.loadModels(tracks.map((t) => t.model));
6445
7124
  await v.warm(tracks);
6446
- } catch (err) {
6447
- console.warn("[dtm] voice preload failed", err);
7125
+ } catch (err2) {
7126
+ console.warn("[dtm] voice preload failed", err2);
6448
7127
  } finally {
6449
7128
  overlay.remove();
6450
7129
  }
6451
7130
  if (!playing || activePlayer !== instance) return;
6452
7131
  }
6453
7132
  seq.start(0);
6454
- if (streaming) ensureVoices().startStream(tracks, seq.getStartTime());
7133
+ if (streaming) {
7134
+ ensureVoices().startStream(tracks, seq.getStartTime(), {
7135
+ isAudible: (t) => !mutedTracks.has(Number(t.id))
7136
+ });
7137
+ }
6455
7138
  };
6456
7139
  const play = () => {
6457
7140
  if (playing || trackIndices.length === 0) return;
@@ -6785,8 +7468,6 @@ var DEFAULT_CDN = {
6785
7468
  soundFont: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont.mjs",
6786
7469
  soundFontDrum: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_drum.mjs",
6787
7470
  soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs",
6788
- parseChord: "https://rpgen3.github.io/piano/mjs/parseChord.mjs",
6789
- parseChords: "https://rpgen3.github.io/piano/mjs/parseChords.mjs",
6790
7471
  midiParser: "https://cdn.jsdelivr.net/npm/midi-parser-js@4.0.4/+esm"
6791
7472
  };
6792
7473
  var SOUNDFONT_NAME = "FluidR3_GM_sf2_file";
@@ -6830,24 +7511,6 @@ var createDtmStudio = async (options = {}) => {
6830
7511
  eng.SoundFont_drum ?? importFrom(cdn.soundFontDrum, "SoundFont_drum"),
6831
7512
  eng.SoundFont_list ?? importFrom(cdn.soundFontList, "SoundFont_list")
6832
7513
  ]);
6833
- let parseChord = eng.parseChord;
6834
- let parseChords = eng.parseChords;
6835
- if (features.chord && (!parseChord || !parseChords)) {
6836
- try {
6837
- [parseChord, parseChords] = await Promise.all([
6838
- parseChord ?? importFrom(
6839
- cdn.parseChord,
6840
- "parseChord"
6841
- ),
6842
- parseChords ?? importFrom(
6843
- cdn.parseChords,
6844
- "parseChords"
6845
- )
6846
- ]);
6847
- } catch (e) {
6848
- console.warn("[dtm] \u30B3\u30FC\u30C9\u89E3\u6790\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F", e);
6849
- }
6850
- }
6851
7514
  let midiParser = null;
6852
7515
  let parseMidi;
6853
7516
  if (features.midi) {
@@ -7033,8 +7696,6 @@ var createDtmStudio = async (options = {}) => {
7033
7696
  onPlayNote: playNote,
7034
7697
  onPlayDrum: playDrum,
7035
7698
  singingVoices,
7036
- parseChord,
7037
- parseChords,
7038
7699
  parseMidi,
7039
7700
  onToggleRecord,
7040
7701
  ...dawOverrides
@@ -7056,15 +7717,32 @@ var createDtmStudio = async (options = {}) => {
7056
7717
  editorPresetSelects.set(target, select);
7057
7718
  select.addEventListener("change", async () => {
7058
7719
  if (!select) return;
7059
- daw.setInstrument(select.value);
7060
- await loadPreset(select.value, trackIds);
7720
+ const wasPlaying = daw.getPlaybackState() === "playing";
7721
+ if (wasPlaying) {
7722
+ daw.pause();
7723
+ }
7724
+ const overlay = showLoadingOverlay(target);
7725
+ daw.setLoading?.(true);
7726
+ try {
7727
+ daw.setInstrument(select.value);
7728
+ await loadPreset(select.value, trackIds);
7729
+ } finally {
7730
+ overlay.remove();
7731
+ daw.setLoading?.(false);
7732
+ if (wasPlaying) {
7733
+ daw.play();
7734
+ }
7735
+ }
7061
7736
  });
7062
7737
  }
7063
7738
  const daw = mountDAW(target, base);
7064
7739
  mountedEditors.push(daw);
7065
7740
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
7066
7741
  daw.setInstrument(presetKey);
7067
- void loadPreset(presetKey, trackIds);
7742
+ daw.setLoading?.(true);
7743
+ void loadPreset(presetKey, trackIds).finally(() => {
7744
+ daw.setLoading?.(false);
7745
+ });
7068
7746
  const destroy = () => {
7069
7747
  daw.destroy();
7070
7748
  select?.remove();