@onjmin/dtm 0.1.35 → 0.1.37

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
@@ -7,25 +7,146 @@ function createAudioContext() {
7
7
  drumGainNode.connect(audioCtx.destination);
8
8
  return { audioCtx, gainNode, drumGainNode };
9
9
  }
10
- async function fetchSoundFontList(ttl) {
11
- const res = await fetch(`https://rpgen3.github.io/soundfont/list/${ttl}.txt`);
12
- const str = await res.text();
13
- return str.trim().split("\n");
14
- }
10
+ var FONT_NAME_SURIKOV = `0000 Acoustic Grand Piano
11
+ 0010 Bright Acoustic Piano
12
+ 0020 Electric Grand Piano
13
+ 0030 Honky-tonk Piano
14
+ 0040 Electric Piano 1
15
+ 0050 Electric Piano 2
16
+ 0060 Harpsichord
17
+ 0070 Clavinet
18
+ 0080 Celesta
19
+ 0090 Glockenspiel
20
+ 0100 Music Box
21
+ 0110 Vibraphone
22
+ 0120 Marimba
23
+ 0130 Xylophone
24
+ 0140 Tubular Bells
25
+ 0150 Dulcimer
26
+ 0160 Drawbar Organ
27
+ 0170 Percussive Organ
28
+ 0180 Rock Organ
29
+ 0190 Church Organ
30
+ 0200 Reed Organ
31
+ 0210 Accordion
32
+ 0220 Harmonica
33
+ 0230 Tango Accordion
34
+ 0240 Acoustic Guitar (nylon)
35
+ 0250 Acoustic Guitar (steel)
36
+ 0260 Electric Guitar (jazz)
37
+ 0270 Electric Guitar (clean)
38
+ 0280 Electric Guitar (muted)
39
+ 0290 Overdriven Guitar
40
+ 0300 Distortion Guitar
41
+ 0310 Guitar Harmonics
42
+ 0320 Acoustic Bass
43
+ 0330 Electric Bass (finger)
44
+ 0340 Electric Bass (pick)
45
+ 0350 Fretless Bass
46
+ 0360 Slap Bass 1
47
+ 0370 Slap Bass 2
48
+ 0380 Synth Bass 1
49
+ 0390 Synth Bass 2
50
+ 0400 Violin
51
+ 0410 Viola
52
+ 0420 Cello
53
+ 0430 Contrabass
54
+ 0440 Tremolo Strings
55
+ 0450 Pizzicato Strings
56
+ 0460 Orchestral Harp
57
+ 0470 Timpani
58
+ 0480 String Ensemble 1
59
+ 0490 String Ensemble 2
60
+ 0500 Synth Strings 1
61
+ 0510 Synth Strings 2
62
+ 0520 Choir Aahs
63
+ 0530 Voice Oohs
64
+ 0540 Synth Choir
65
+ 0550 Orchestra Hit
66
+ 0560 Trumpet
67
+ 0570 Trombone
68
+ 0580 Tuba
69
+ 0590 Muted Trumpet
70
+ 0600 French Horn
71
+ 0610 Brass Section
72
+ 0620 Synth Brass 1
73
+ 0630 Synth Brass 2
74
+ 0640 Soprano Sax
75
+ 0650 Alto Sax
76
+ 0660 Tenor Sax
77
+ 0670 Baritone Sax
78
+ 0680 Oboe
79
+ 0690 English Horn
80
+ 0700 Bassoon
81
+ 0710 Clarinet
82
+ 0720 Piccolo
83
+ 0730 Flute
84
+ 0740 Recorder
85
+ 0750 Pan Flute
86
+ 0760 Blown bottle
87
+ 0770 Shakuhachi
88
+ 0780 Whistle
89
+ 0790 Ocarina
90
+ 0800 Lead 1 (square)
91
+ 0810 Lead 2 (sawtooth)
92
+ 0820 Lead 3 (calliope)
93
+ 0830 Lead 4 (chiff)
94
+ 0840 Lead 5 (charang)
95
+ 0850 Lead 6 (voice)
96
+ 0860 Lead 7 (fifths)
97
+ 0870 Lead 8 (bass + lead)
98
+ 0880 Pad 1 (new age)
99
+ 0890 Pad 2 (warm)
100
+ 0900 Pad 3 (polysynth)
101
+ 0910 Pad 4 (choir)
102
+ 0920 Pad 5 (bowed)
103
+ 0930 Pad 6 (metallic)
104
+ 0940 Pad 7 (halo)
105
+ 0950 Pad 8 (sweep)
106
+ 0960 FX 1 (rain)
107
+ 0970 FX 2 (soundtrack)
108
+ 0980 FX 3 (crystal)
109
+ 0990 FX 4 (atmosphere)
110
+ 1000 FX 5 (brightness)
111
+ 1010 FX 6 (goblins)
112
+ 1020 FX 7 (echoes)
113
+ 1030 FX 8 (sci-fi)
114
+ 1040 Sitar
115
+ 1050 Banjo
116
+ 1060 Shamisen
117
+ 1070 Koto
118
+ 1080 Kalimba
119
+ 1090 Bagpipe
120
+ 1100 Fiddle
121
+ 1110 Shanai
122
+ 1120 Tinkle Bell
123
+ 1130 Agogo
124
+ 1140 Steel Drums
125
+ 1150 Woodblock
126
+ 1160 Taiko Drum
127
+ 1170 Melodic Tom
128
+ 1180 Synth Drum
129
+ 1190 Reverse Cymbal
130
+ 1200 Guitar Fret Noise
131
+ 1210 Breath Noise
132
+ 1220 Seashore
133
+ 1230 Bird Tweet
134
+ 1240 Telephone Ring
135
+ 1250 Helicopter
136
+ 1260 Applause
137
+ 1270 Gunshot`;
15
138
  async function buildNameToKeyMapping() {
16
139
  const nameToKey = {};
17
- try {
18
- const fontNames = await fetchSoundFontList("fontName_surikov");
19
- fontNames.forEach((line) => {
20
- const [key, ...nameParts] = line.split(" ");
21
- const name = nameParts.join(" ");
22
- nameToKey[name] = key;
23
- });
24
- } catch (e) {
25
- console.error("Failed to build name-to-key mapping:", e);
140
+ for (const line of FONT_NAME_SURIKOV.trim().split("\n")) {
141
+ const spaceIdx = line.indexOf(" ");
142
+ if (spaceIdx === -1) continue;
143
+ const key = line.slice(0, spaceIdx);
144
+ const name = line.slice(spaceIdx + 1);
145
+ nameToKey[name] = key;
26
146
  }
27
147
  return nameToKey;
28
148
  }
149
+ var GM_INSTRUMENT_NAMES = FONT_NAME_SURIKOV.trim().split("\n").map((line) => line.slice(line.indexOf(" ") + 1));
29
150
 
30
151
  // node_modules/.pnpm/@onjmin+chord-parser@1.0.2/node_modules/@onjmin/chord-parser/dist/index.mjs
31
152
  var SHARP_NAMES = [
@@ -1103,14 +1224,17 @@ var buildUI = (target, options) => {
1103
1224
  target.innerHTML = `
1104
1225
  <div class="dtm-daw" data-dtm="root">
1105
1226
  <div class="dtm-topbar" data-dtm="transport">
1106
- <button class="dtm-iconbtn" data-dtm="prev-bar" title="1\u5C0F\u7BC0\u524D">${icon("chevronLeft")}</button>
1107
- <button class="dtm-play" data-dtm="play" disabled>${icon("play")}<span>\u8A66\u8074</span></button>
1108
- <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
1109
- <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
1110
- <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
1111
- <span class="dtm-grow"></span>
1112
- <span class="dtm-label">BPM</span>
1113
- <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
1227
+ <div class="dtm-topbar-row1">
1228
+ <button class="dtm-iconbtn" data-dtm="prev-bar" title="1\u5C0F\u7BC0\u524D">${icon("chevronLeft")}</button>
1229
+ <button class="dtm-play" data-dtm="play" disabled>${icon("play")}<span>\u8A66\u8074</span></button>
1230
+ <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
1231
+ <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
1232
+ <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
1233
+ <span class="dtm-grow"></span>
1234
+ <span class="dtm-label">BPM</span>
1235
+ <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
1236
+ </div>
1237
+ <div class="dtm-tracks" data-dtm="track-tabs"></div>
1114
1238
  </div>
1115
1239
 
1116
1240
  <div class="dtm-tooldock">
@@ -1133,8 +1257,6 @@ var buildUI = (target, options) => {
1133
1257
  </select>
1134
1258
  </div>
1135
1259
 
1136
- <div class="dtm-tracks" data-dtm="track-tabs"></div>
1137
-
1138
1260
  <div class="dtm-roll-wrap">
1139
1261
  <div class="dtm-roll" data-dtm="roll">
1140
1262
  <div data-dtm="wrapper" style="position:absolute;inset:0;"></div>
@@ -4165,6 +4287,7 @@ var PITCH_MAP2 = {
4165
4287
  };
4166
4288
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
4167
4289
  var META_DIRECTIVE = /#(inst|drum|volume|drumvolume|mode)=([\w-]+)/gi;
4290
+ var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
4168
4291
  var parseMmlMeta = (mml) => {
4169
4292
  const meta = {};
4170
4293
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -4183,9 +4306,17 @@ var parseMmlMeta = (mml) => {
4183
4306
  }
4184
4307
  }
4185
4308
  }
4309
+ for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
4310
+ const idx = Number.parseInt(m[1], 10);
4311
+ const name = m[2].trim();
4312
+ if (!Number.isNaN(idx) && name) {
4313
+ meta.trackInstruments ??= {};
4314
+ meta.trackInstruments[idx] = name;
4315
+ }
4316
+ }
4186
4317
  return meta;
4187
4318
  };
4188
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "");
4319
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "");
4189
4320
  var formatMmlMeta = (meta, space = "") => {
4190
4321
  const parts = [];
4191
4322
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -4194,6 +4325,11 @@ var formatMmlMeta = (meta, space = "") => {
4194
4325
  if (meta.drumVolume !== void 0)
4195
4326
  parts.push(`#drumvolume=${meta.drumVolume}`);
4196
4327
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
4328
+ if (meta.trackInstruments) {
4329
+ for (const [idx, name] of Object.entries(meta.trackInstruments)) {
4330
+ if (name) parts.push(`#t${idx}inst=${name}`);
4331
+ }
4332
+ }
4197
4333
  return parts.join(space);
4198
4334
  };
4199
4335
  var parseMML = (mml, options = {}) => {
@@ -4815,7 +4951,7 @@ var DAW_CSS = `
4815
4951
  top: 0;
4816
4952
  z-index: 20;
4817
4953
  display: flex;
4818
- flex-wrap: nowrap;
4954
+ flex-wrap: wrap;
4819
4955
  align-items: center;
4820
4956
  gap: var(--dtm-gap);
4821
4957
  padding: 6px;
@@ -4825,18 +4961,19 @@ var DAW_CSS = `
4825
4961
  inset 0 0 0 2px var(--c-black),
4826
4962
  0 0 0 2px var(--dtm-success),
4827
4963
  4px 4px 0 var(--c-black);
4964
+ }
4965
+ .dtm-topbar-row1 {
4966
+ display: flex;
4967
+ align-items: center;
4968
+ gap: var(--dtm-gap);
4969
+ flex-basis: 100%;
4970
+ min-width: 0;
4828
4971
  overflow-x: auto;
4829
4972
  scrollbar-width: none;
4830
4973
  }
4831
- .dtm-topbar::-webkit-scrollbar {
4832
- display: none;
4833
- }
4834
- .dtm-topbar > * {
4835
- flex-shrink: 0;
4836
- }
4837
- .dtm-topbar > .dtm-grow {
4838
- flex-shrink: 1;
4839
- }
4974
+ .dtm-topbar-row1::-webkit-scrollbar { display: none; }
4975
+ .dtm-topbar-row1 > * { flex-shrink: 0; }
4976
+ .dtm-topbar-row1 > .dtm-grow { flex-shrink: 1; }
4840
4977
 
4841
4978
  /* PLAY\u30DC\u30BF\u30F3 \u2014 \u30B2\u30FC\u30E0\u306E\u300C\u6C7A\u5B9A\u30DC\u30BF\u30F3\u300D\u7684\u5B58\u5728\u611F */
4842
4979
  .dtm-play {
@@ -5026,46 +5163,39 @@ var DAW_CSS = `
5026
5163
  .dtm-modebtn--active { background: var(--dtm-primary); color: var(--dtm-pfg); }
5027
5164
  .dtm-modebtn:not(.dtm-modebtn--active):active { background: var(--dtm-border2); }
5028
5165
 
5029
- /* \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 */
5166
+ /* \u2500\u2500\u2500 \u30C8\u30E9\u30C3\u30AF\u30D4\u30EB\uFF08\u756A\u53F7\u30DC\u30BF\u30F3\u3001\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u30D0\u30FC2\u884C\u76EE\uFF09 \u2500\u2500\u2500 */
5030
5167
  .dtm-tracks {
5168
+ flex-basis: 100%;
5031
5169
  display: flex;
5032
- flex-wrap: wrap;
5033
- gap: var(--dtm-gap);
5170
+ flex-wrap: nowrap;
5171
+ gap: 3px;
5034
5172
  }
5035
5173
  .dtm-pill {
5036
5174
  --dtm-pill-color: var(--dtm-primary);
5037
5175
  display: inline-flex;
5038
5176
  align-items: center;
5039
- gap: 8px;
5040
- flex: 1 1 auto;
5041
5177
  justify-content: center;
5042
- min-height: 42px;
5043
- padding: 0 12px;
5044
- border: 2px solid var(--dtm-border2);
5045
- background: var(--dtm-deep);
5046
- color: var(--dtm-muted);
5178
+ flex: 1 1 0;
5179
+ min-width: 0;
5180
+ height: 26px;
5181
+ padding: 0;
5182
+ border: 2px solid var(--c-black);
5183
+ background: color-mix(in srgb, var(--dtm-pill-color) 40%, black);
5184
+ color: var(--c-white);
5047
5185
  font-family: var(--dtm-font);
5048
- font-size: 13px;
5049
- text-transform: uppercase;
5050
- letter-spacing: .1em;
5186
+ font-size: 11px;
5187
+ font-weight: bold;
5051
5188
  cursor: pointer;
5052
- box-shadow: 3px 3px 0 var(--c-black);
5053
- }
5054
- .dtm-pill .dtm-dot {
5055
- width: 8px; height: 8px;
5056
- background: var(--dtm-pill-color);
5057
- flex: 0 0 auto;
5058
- box-shadow: 1px 1px 0 var(--c-black);
5189
+ box-shadow: 2px 2px 0 var(--c-black);
5190
+ opacity: 0.7;
5059
5191
  }
5060
- /* \u30A2\u30AF\u30C6\u30A3\u30D6\u9078\u629E = \u91D1\u8272\u30CF\u30A4\u30E9\u30A4\u30C8 + \u30AB\u30FC\u30BD\u30EB */
5192
+ /* \u30A2\u30AF\u30C6\u30A3\u30D6\u9078\u629E = \u4E0D\u900F\u660E + \u91D1\u67A0 */
5061
5193
  .dtm-pill--active {
5194
+ opacity: 1;
5062
5195
  border-color: var(--dtm-gold);
5063
- color: var(--dtm-gold);
5064
- background: var(--dtm-surface);
5065
- box-shadow: 0 0 0 2px var(--dtm-gold), 3px 3px 0 var(--c-black);
5196
+ box-shadow: 0 0 0 1px var(--dtm-gold), 2px 2px 0 var(--c-black);
5066
5197
  }
5067
- .dtm-pill--active::before { content: "\u25BA "; font-size: 10px; }
5068
- .dtm-pill:not(.dtm-pill--active):active { transform: translate(3px,3px); box-shadow: none; }
5198
+ .dtm-pill:not(.dtm-pill--active):active { transform: translate(2px,2px); box-shadow: none; }
5069
5199
 
5070
5200
  /* \u2500\u2500\u2500 \u30D4\u30A2\u30CE\u30ED\u30FC\u30EB\uFF08\u30C8\u30E9\u30C3\u30AB\u30FC\u98A8\uFF09 \u2500\u2500\u2500 */
5071
5201
  .dtm-roll-wrap { display: flex; gap: var(--dtm-gap); }
@@ -5209,7 +5339,7 @@ var DAW_CSS = `
5209
5339
  }
5210
5340
  .dtm-overlay[hidden] { display: none; }
5211
5341
  .dtm-overlay::before {
5212
- content: 'NOW LOADING';
5342
+ content: '\u30ED\u30FC\u30C9\u4E2D';
5213
5343
  font-family: var(--dtm-font);
5214
5344
  font-size: 13px;
5215
5345
  color: var(--dtm-primary);
@@ -5402,6 +5532,15 @@ var DAW_CSS = `
5402
5532
  justify-content: center;
5403
5533
  }
5404
5534
 
5535
+ .dtm-confirm-footer {
5536
+ padding: 8px 12px;
5537
+ border-top: 2px solid var(--c-black);
5538
+ background: var(--dtm-deep);
5539
+ display: flex;
5540
+ gap: 8px;
5541
+ justify-content: flex-end;
5542
+ }
5543
+
5405
5544
  .dtm-modal {
5406
5545
  max-width: 500px;
5407
5546
  width: 100%;
@@ -6424,9 +6563,11 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6424
6563
  const mmlInfoItem = makeMenuItem("MML\u66F8\u5F0F\u3068\u306F");
6425
6564
  const embedItem = makeMenuItem("\u57CB\u3081\u8FBC\u3080");
6426
6565
  const copyMmlItem = makeMenuItem("MML\u30B3\u30D4\u30FC");
6427
- menuDropdown.appendChild(showMmlItem);
6428
- menuDropdown.appendChild(mmlInfoItem);
6429
- menuDropdown.appendChild(embedItem);
6566
+ if (!options._skipInfoModals) {
6567
+ menuDropdown.appendChild(showMmlItem);
6568
+ menuDropdown.appendChild(mmlInfoItem);
6569
+ menuDropdown.appendChild(embedItem);
6570
+ }
6430
6571
  menuDropdown.appendChild(copyMmlItem);
6431
6572
  menuContainer.appendChild(menuDropdown);
6432
6573
  mmlHeader.appendChild(menuContainer);
@@ -6580,6 +6721,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6580
6721
  volume: trackVolume,
6581
6722
  // 解説モーダル内の試聴サンプルは規約同意を要求しない。
6582
6723
  skipConsent: true,
6724
+ // 再帰的なモーダル生成を防ぐ。
6725
+ _skipInfoModals: true,
6583
6726
  onStop: () => {
6584
6727
  if (activeSampleBtn === el) resetSampleBtn(el);
6585
6728
  }
@@ -6589,65 +6732,67 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6589
6732
  });
6590
6733
  }
6591
6734
  };
6592
- showMmlItem.addEventListener("click", (e) => {
6593
- e.stopPropagation();
6594
- toggleMenu(false);
6595
- const modalBody = openInfoModal("MML\u3092\u8868\u793A");
6596
- const desc = doc.createElement("p");
6597
- desc.textContent = "\u3053\u306EMML\u3092\u30B3\u30D4\u30FC\u3057\u3066\u3001\u4ED6\u306E\u30D7\u30EC\u30A4\u30E4\u30FC\u3084\u5171\u6709URL\u306B\u8CBC\u308A\u4ED8\u3051\u3066\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002";
6598
- desc.style.marginBottom = "8px";
6599
- modalBody.appendChild(desc);
6600
- const sourceMml = options.getMml?.() ?? mml;
6601
- const displayMml = sourceMml.split(";").map((s) => s.trim()).filter((s) => s.length > 0).join(";\n");
6602
- const pre = doc.createElement("pre");
6603
- pre.textContent = displayMml;
6604
- pre.style.whiteSpace = "pre-wrap";
6605
- pre.style.wordBreak = "break-all";
6606
- pre.style.cursor = "text";
6607
- pre.addEventListener("click", () => {
6608
- const range = doc.createRange();
6609
- range.selectNodeContents(pre);
6610
- const sel = doc.defaultView?.getSelection();
6611
- sel?.removeAllRanges();
6612
- sel?.addRange(range);
6613
- });
6614
- modalBody.appendChild(pre);
6615
- appendCopyButton(modalBody, sourceMml);
6616
- });
6617
- mmlInfoItem.addEventListener("click", (e) => {
6618
- e.stopPropagation();
6619
- toggleMenu(false);
6620
- const modalBody = openInfoModal("MML\u306E\u66F8\u304D\u65B9\u89E3\u8AAC");
6621
- modalBody.innerHTML = MML_INFO_HTML;
6622
- wireSampleButtons(modalBody);
6623
- });
6624
- embedItem.addEventListener("click", async (e) => {
6625
- e.stopPropagation();
6626
- toggleMenu(false);
6627
- const modalBody = openInfoModal("\u57CB\u3081\u8FBC\u307F");
6628
- const loading = doc.createElement("p");
6629
- loading.textContent = "\u751F\u6210\u4E2D...";
6630
- modalBody.appendChild(loading);
6631
- try {
6632
- const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6633
- const payload = await encodeMml(mml);
6634
- const url2 = `${embedBase}#${payload}`;
6635
- const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6636
- if (!modalBody.isConnected) return;
6637
- loading.remove();
6735
+ if (!options._skipInfoModals) {
6736
+ showMmlItem.addEventListener("click", (e) => {
6737
+ e.stopPropagation();
6738
+ toggleMenu(false);
6739
+ const modalBody = openInfoModal("MML\u3092\u8868\u793A");
6638
6740
  const desc = doc.createElement("p");
6639
- desc.textContent = "\u3053\u306EHTML\u3092\u30D6\u30ED\u30B0\u3084\u30B5\u30A4\u30C8\u306B\u8CBC\u308A\u4ED8\u3051\u308B\u3068\u3001\u30D7\u30EC\u30A4\u30E4\u30FC\u3092\u305D\u306E\u307E\u307E\u57CB\u3081\u8FBC\u3081\u307E\u3059\u3002";
6741
+ desc.textContent = "\u3053\u306EMML\u3092\u30B3\u30D4\u30FC\u3057\u3066\u3001\u4ED6\u306E\u30D7\u30EC\u30A4\u30E4\u30FC\u3084\u5171\u6709URL\u306B\u8CBC\u308A\u4ED8\u3051\u3066\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002";
6742
+ desc.style.marginBottom = "8px";
6743
+ modalBody.appendChild(desc);
6744
+ const sourceMml = options.getMml?.() ?? mml;
6745
+ const displayMml = sourceMml.split(";").map((s) => s.trim()).filter((s) => s.length > 0).join(";\n");
6640
6746
  const pre = doc.createElement("pre");
6641
- pre.textContent = snippet;
6747
+ pre.textContent = displayMml;
6642
6748
  pre.style.whiteSpace = "pre-wrap";
6643
6749
  pre.style.wordBreak = "break-all";
6644
- modalBody.append(desc, pre);
6645
- appendCopyButton(modalBody, snippet);
6646
- } catch (err2) {
6647
- console.error("[dtm] failed to generate embed snippet", err2);
6648
- if (modalBody.isConnected) loading.textContent = "\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F";
6649
- }
6650
- });
6750
+ pre.style.cursor = "text";
6751
+ pre.addEventListener("click", () => {
6752
+ const range = doc.createRange();
6753
+ range.selectNodeContents(pre);
6754
+ const sel = doc.defaultView?.getSelection();
6755
+ sel?.removeAllRanges();
6756
+ sel?.addRange(range);
6757
+ });
6758
+ modalBody.appendChild(pre);
6759
+ appendCopyButton(modalBody, sourceMml);
6760
+ });
6761
+ mmlInfoItem.addEventListener("click", (e) => {
6762
+ e.stopPropagation();
6763
+ toggleMenu(false);
6764
+ const modalBody = openInfoModal("MML\u306E\u66F8\u304D\u65B9\u89E3\u8AAC");
6765
+ modalBody.innerHTML = MML_INFO_HTML;
6766
+ wireSampleButtons(modalBody);
6767
+ });
6768
+ embedItem.addEventListener("click", async (e) => {
6769
+ e.stopPropagation();
6770
+ toggleMenu(false);
6771
+ const modalBody = openInfoModal("\u57CB\u3081\u8FBC\u307F");
6772
+ const loading = doc.createElement("p");
6773
+ loading.textContent = "\u751F\u6210\u4E2D...";
6774
+ modalBody.appendChild(loading);
6775
+ try {
6776
+ const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6777
+ const payload = await encodeMml(mml);
6778
+ const url2 = `${embedBase}#${payload}`;
6779
+ const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6780
+ if (!modalBody.isConnected) return;
6781
+ loading.remove();
6782
+ const desc = doc.createElement("p");
6783
+ desc.textContent = "\u3053\u306EHTML\u3092\u30D6\u30ED\u30B0\u3084\u30B5\u30A4\u30C8\u306B\u8CBC\u308A\u4ED8\u3051\u308B\u3068\u3001\u30D7\u30EC\u30A4\u30E4\u30FC\u3092\u305D\u306E\u307E\u307E\u57CB\u3081\u8FBC\u3081\u307E\u3059\u3002";
6784
+ const pre = doc.createElement("pre");
6785
+ pre.textContent = snippet;
6786
+ pre.style.whiteSpace = "pre-wrap";
6787
+ pre.style.wordBreak = "break-all";
6788
+ modalBody.append(desc, pre);
6789
+ appendCopyButton(modalBody, snippet);
6790
+ } catch (err2) {
6791
+ console.error("[dtm] failed to generate embed snippet", err2);
6792
+ if (modalBody.isConnected) loading.textContent = "\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F";
6793
+ }
6794
+ });
6795
+ }
6651
6796
  copyMmlItem.addEventListener("click", async (e) => {
6652
6797
  e.stopPropagation();
6653
6798
  const success = await copyToClipboard(doc, options.getMml?.() ?? mml);
@@ -7244,8 +7389,8 @@ var MIDI_INFO_HTML = `
7244
7389
 
7245
7390
  <h4>3. \u30E2\u30FC\u30C9\u306B\u3088\u308B\u53D6\u308A\u8FBC\u307F\u65B9\u306E\u9055\u3044</h4>
7246
7391
  <ul>
7247
- <li><strong>SIMPLE</strong>: \u5404\u30C8\u30E9\u30C3\u30AF\u306E\u7279\u5FB4\u304B\u3089\u3001\u30E1\u30ED\u30C7\u30A3\u30FC\u30FB\u30B5\u30D6\u30E1\u30ED\u30FB\u30D9\u30FC\u30B9\u30FB\u4F34\u594F\u306E4\u3064\u306E\u5F79\u5272\u306B\u81EA\u52D5\u3067\u632F\u308A\u5206\u3051\u3089\u308C\u307E\u3059\u3002</li>
7248
- <li><strong>ADVANCED</strong>: MIDI\u306E\u30C8\u30E9\u30C3\u30AF\u69CB\u6210\u304C\u305D\u306E\u307E\u307E\u53CD\u6620\u3055\u308C\u307E\u3059\uFF081\u5BFE1\uFF09\u3002</li>
7392
+ <li><strong>\u521D\u5FC3\u8005\u30E2\u30FC\u30C9</strong>: \u5404\u30C8\u30E9\u30C3\u30AF\u306E\u7279\u5FB4\u304B\u3089\u3001\u30E1\u30ED\u30C7\u30A3\u30FC\u30FB\u30B5\u30D6\u30E1\u30ED\u30FB\u30D9\u30FC\u30B9\u30FB\u4F34\u594F\u306E4\u3064\u306E\u5F79\u5272\u306B\u81EA\u52D5\u3067\u632F\u308A\u5206\u3051\u3089\u308C\u307E\u3059\u3002</li>
7393
+ <li><strong>\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9</strong>: MIDI\u306E\u30C8\u30E9\u30C3\u30AF\u69CB\u6210\u304C\u305D\u306E\u307E\u307E\u53CD\u6620\u3055\u308C\u307E\u3059\uFF081\u5BFE1\uFF09\u3002</li>
7249
7394
  </ul>
7250
7395
 
7251
7396
  <h4>4. MIDI\u30D5\u30A1\u30A4\u30EB\u3092\u624B\u306B\u5165\u308C\u308B</h4>
@@ -7297,105 +7442,105 @@ var TRACKS_SIMPLE = [
7297
7442
  var TRACKS_ADVANCED = [
7298
7443
  {
7299
7444
  id: "t0",
7300
- name: "TRACK 01",
7445
+ name: "\u30C8\u30E9\u30C3\u30AF1",
7301
7446
  color: [41, 173, 255],
7302
7447
  instrument: 0,
7303
7448
  volume: 100
7304
7449
  },
7305
7450
  {
7306
7451
  id: "t1",
7307
- name: "TRACK 02",
7452
+ name: "\u30C8\u30E9\u30C3\u30AF2",
7308
7453
  color: [0, 228, 54],
7309
7454
  instrument: 1,
7310
7455
  volume: 100
7311
7456
  },
7312
7457
  {
7313
7458
  id: "t2",
7314
- name: "TRACK 03",
7459
+ name: "\u30C8\u30E9\u30C3\u30AF3",
7315
7460
  color: [255, 119, 168],
7316
7461
  instrument: 2,
7317
7462
  volume: 100
7318
7463
  },
7319
7464
  {
7320
7465
  id: "t3",
7321
- name: "TRACK 04",
7466
+ name: "\u30C8\u30E9\u30C3\u30AF4",
7322
7467
  color: [255, 163, 0],
7323
7468
  instrument: 3,
7324
7469
  volume: 100
7325
7470
  },
7326
7471
  {
7327
7472
  id: "t4",
7328
- name: "TRACK 05",
7473
+ name: "\u30C8\u30E9\u30C3\u30AF5",
7329
7474
  color: [255, 236, 39],
7330
7475
  instrument: 4,
7331
7476
  volume: 100
7332
7477
  },
7333
7478
  {
7334
7479
  id: "t5",
7335
- name: "TRACK 06",
7480
+ name: "\u30C8\u30E9\u30C3\u30AF6",
7336
7481
  color: [131, 118, 156],
7337
7482
  instrument: 5,
7338
7483
  volume: 100
7339
7484
  },
7340
7485
  {
7341
7486
  id: "t6",
7342
- name: "TRACK 07",
7487
+ name: "\u30C8\u30E9\u30C3\u30AF7",
7343
7488
  color: [255, 0, 77],
7344
7489
  instrument: 6,
7345
7490
  volume: 100
7346
7491
  },
7347
7492
  {
7348
7493
  id: "t7",
7349
- name: "TRACK 08",
7494
+ name: "\u30C8\u30E9\u30C3\u30AF8",
7350
7495
  color: [255, 204, 170],
7351
7496
  instrument: 7,
7352
7497
  volume: 100
7353
7498
  },
7354
7499
  {
7355
7500
  id: "t8",
7356
- name: "TRACK 09",
7501
+ name: "\u30C8\u30E9\u30C3\u30AF9",
7357
7502
  color: [194, 195, 199],
7358
7503
  instrument: 8,
7359
7504
  volume: 100
7360
7505
  },
7361
7506
  {
7362
7507
  id: "t9",
7363
- name: "TRACK 10",
7508
+ name: "\u30C8\u30E9\u30C3\u30AF10",
7364
7509
  color: [0, 135, 81],
7365
7510
  instrument: 9,
7366
7511
  volume: 100
7367
7512
  },
7368
7513
  {
7369
7514
  id: "t10",
7370
- name: "TRACK 11",
7515
+ name: "\u30C8\u30E9\u30C3\u30AF11",
7371
7516
  color: [171, 82, 54],
7372
7517
  instrument: 10,
7373
7518
  volume: 100
7374
7519
  },
7375
7520
  {
7376
7521
  id: "t11",
7377
- name: "TRACK 12",
7522
+ name: "\u30C8\u30E9\u30C3\u30AF12",
7378
7523
  color: [126, 37, 83],
7379
7524
  instrument: 11,
7380
7525
  volume: 100
7381
7526
  },
7382
7527
  {
7383
7528
  id: "t12",
7384
- name: "TRACK 13",
7529
+ name: "\u30C8\u30E9\u30C3\u30AF13",
7385
7530
  color: [255, 241, 232],
7386
7531
  instrument: 12,
7387
7532
  volume: 100
7388
7533
  },
7389
7534
  {
7390
7535
  id: "t13",
7391
- name: "TRACK 14",
7536
+ name: "\u30C8\u30E9\u30C3\u30AF14",
7392
7537
  color: [120, 200, 255],
7393
7538
  instrument: 13,
7394
7539
  volume: 100
7395
7540
  },
7396
7541
  {
7397
7542
  id: "t14",
7398
- name: "TRACK 15",
7543
+ name: "\u30C8\u30E9\u30C3\u30AF15",
7399
7544
  color: [100, 255, 160],
7400
7545
  instrument: 14,
7401
7546
  volume: 100
@@ -7409,6 +7554,13 @@ var LYRIC_MODEL_LABELS2 = {
7409
7554
  };
7410
7555
  var lyricModelLabel = (model) => LYRIC_MODEL_LABELS2[model] ?? model;
7411
7556
  var clamp3 = (v, min, max) => Math.min(Math.max(v, min), max);
7557
+ var normalizeInstrumentName = (name) => {
7558
+ if (!name) return "";
7559
+ const stripped = name.replace(/\s+/g, "").toLowerCase();
7560
+ return GM_INSTRUMENT_NAMES.find(
7561
+ (n) => n.replace(/\s+/g, "").toLowerCase() === stripped
7562
+ ) ?? name;
7563
+ };
7412
7564
  var mountDAW = (target, options = {}) => {
7413
7565
  injectStyles();
7414
7566
  const getAudioTime = options.getAudioTime ?? (() => performance.now() / 1e3);
@@ -7448,7 +7600,8 @@ var mountDAW = (target, options = {}) => {
7448
7600
  let snapGridSteps = 12;
7449
7601
  const gridLineSteps = 48;
7450
7602
  let currentOffsetX = 0;
7451
- let currentOffsetY = (104 - 1 - 60) * renderConfig.keyHeight - 215;
7603
+ const _initPitch = options.initialScrollPitch;
7604
+ let currentOffsetY = _initPitch !== void 0 ? (renderConfig.keyCount - 1 - _initPitch) * renderConfig.keyHeight - 215 : (104 - 1 - 60) * renderConfig.keyHeight - 215;
7452
7605
  let playStartStep = 0;
7453
7606
  let isSolo = false;
7454
7607
  let lyricTrackIndices = /* @__PURE__ */ new Set();
@@ -7461,6 +7614,24 @@ var mountDAW = (target, options = {}) => {
7461
7614
  let copiedNotes = [];
7462
7615
  let trackStates = [];
7463
7616
  let suppressPatch = false;
7617
+ let lyricsDebounceTimer = null;
7618
+ const fireLyricsChange = (t) => {
7619
+ if (!options.onLyricsChange) return;
7620
+ const trackId = t.config.id;
7621
+ const data = {
7622
+ lyrics: t.lyrics,
7623
+ model: t.lyricModel,
7624
+ vocalVolume: t.vocalVolume,
7625
+ vocalGate: t.vocalGate,
7626
+ vocalPan: t.vocalPan,
7627
+ vocalOctave: t.vocalOctave
7628
+ };
7629
+ if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
7630
+ lyricsDebounceTimer = setTimeout(() => {
7631
+ options.onLyricsChange(trackId, data);
7632
+ lyricsDebounceTimer = null;
7633
+ }, 300);
7634
+ };
7464
7635
  const hiddenTracks = /* @__PURE__ */ new Set();
7465
7636
  const audioMutedTracks = /* @__PURE__ */ new Set();
7466
7637
  const createTrackStates = () => {
@@ -7509,7 +7680,8 @@ var mountDAW = (target, options = {}) => {
7509
7680
  vocalVolume: DEFAULT_VOCAL_VOLUME,
7510
7681
  vocalGate: 100,
7511
7682
  vocalPan: 64,
7512
- vocalOctave: 0
7683
+ vocalOctave: 0,
7684
+ trackInstrument: ""
7513
7685
  };
7514
7686
  });
7515
7687
  };
@@ -8215,19 +8387,20 @@ var mountDAW = (target, options = {}) => {
8215
8387
  };
8216
8388
  const updateTrackPanel = () => {
8217
8389
  refs.trackTabs.innerHTML = "";
8218
- for (const t of trackStates) {
8390
+ for (const [i, t] of trackStates.entries()) {
8219
8391
  const [r, g, b] = t.config.color;
8220
8392
  const btn = document.createElement("button");
8221
8393
  btn.className = `dtm-pill ${t.config.id === activeTrackId ? "dtm-pill--active" : ""}`;
8222
8394
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
8223
- btn.innerHTML = `<span class="dtm-dot"></span><span>${t.config.name}</span>`;
8395
+ btn.title = t.config.name;
8396
+ btn.textContent = String(i + 1);
8224
8397
  btn.addEventListener("click", () => switchTrack(t.config.id));
8225
8398
  refs.trackTabs.appendChild(btn);
8226
8399
  }
8227
8400
  const active = getActive();
8228
8401
  refs.trackBody.innerHTML = `
8229
8402
  <div class="dtm-row">
8230
- <span class="dtm-label">velocity</span>
8403
+ <span class="dtm-label">\u30D9\u30ED\u30B7\u30C6\u30A3</span>
8231
8404
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
8232
8405
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
8233
8406
  </div>`;
@@ -8242,6 +8415,59 @@ var mountDAW = (target, options = {}) => {
8242
8415
  active.core.setVolume(active.volume);
8243
8416
  volLabel.textContent = String(active.volume);
8244
8417
  });
8418
+ const instRow = document.createElement("div");
8419
+ instRow.className = "dtm-row";
8420
+ instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
8421
+ const instSel = document.createElement("select");
8422
+ instSel.className = "dtm-select dtm-grow";
8423
+ const defaultOpt = document.createElement("option");
8424
+ defaultOpt.value = "";
8425
+ defaultOpt.textContent = "\u30C7\u30D5\u30A9\u30EB\u30C8\uFF08\u30D7\u30EA\u30BB\u30C3\u30C8\uFF09";
8426
+ instSel.appendChild(defaultOpt);
8427
+ const GM_GROUPS = [
8428
+ "\u30D4\u30A2\u30CE",
8429
+ "\u30AF\u30ED\u30DE\u30C6\u30A3\u30C3\u30AF\u30D1\u30FC\u30AB\u30C3\u30B7\u30E7\u30F3",
8430
+ "\u30AA\u30EB\u30AC\u30F3",
8431
+ "\u30AE\u30BF\u30FC",
8432
+ "\u30D9\u30FC\u30B9",
8433
+ "\u30B9\u30C8\u30EA\u30F3\u30B0\u30B9",
8434
+ "\u30A2\u30F3\u30B5\u30F3\u30D6\u30EB",
8435
+ "\u30D6\u30E9\u30B9",
8436
+ "\u30EA\u30FC\u30C9\uFF08\u6728\u7BA1\uFF09",
8437
+ "\u30D1\u30A4\u30D7",
8438
+ "\u30B7\u30F3\u30BB\u30EA\u30FC\u30C9",
8439
+ "\u30B7\u30F3\u30BB\u30D1\u30C3\u30C9",
8440
+ "\u30B7\u30F3\u30BB\u30A8\u30D5\u30A7\u30AF\u30C8",
8441
+ "\u30A8\u30B9\u30CB\u30C3\u30AF",
8442
+ "\u30D1\u30FC\u30AB\u30C3\u30B7\u30D6",
8443
+ "\u30B5\u30A6\u30F3\u30C9\u30A8\u30D5\u30A7\u30AF\u30C8"
8444
+ ];
8445
+ GM_GROUPS.forEach((groupName, gi) => {
8446
+ const grp = document.createElement("optgroup");
8447
+ grp.label = groupName;
8448
+ for (let j = 0; j < 8; j++) {
8449
+ const name = GM_INSTRUMENT_NAMES[gi * 8 + j];
8450
+ if (!name) break;
8451
+ const o = document.createElement("option");
8452
+ o.value = name;
8453
+ o.textContent = name;
8454
+ grp.appendChild(o);
8455
+ }
8456
+ instSel.appendChild(grp);
8457
+ });
8458
+ instSel.value = normalizeInstrumentName(active.trackInstrument);
8459
+ const syncInstDisabled = () => {
8460
+ instSel.disabled = !!active.lyricModel;
8461
+ instSel.title = active.lyricModel ? "\u6B4C\u8A5E\u30E2\u30FC\u30C9\u306E\u3068\u304D\u306F\u697D\u5668\u3092\u500B\u5225\u6307\u5B9A\u3067\u304D\u307E\u305B\u3093" : "";
8462
+ };
8463
+ syncInstDisabled();
8464
+ instSel.addEventListener("change", () => {
8465
+ active.trackInstrument = instSel.value;
8466
+ const trackIndex = trackStates.indexOf(active);
8467
+ options.onTrackInstrumentChange?.(trackIndex, active.trackInstrument);
8468
+ });
8469
+ instRow.appendChild(instSel);
8470
+ refs.trackBody.appendChild(instRow);
8245
8471
  if (isAdvanced || active.config.id !== "chord") {
8246
8472
  const lyricDiv = document.createElement("div");
8247
8473
  lyricDiv.className = "dtm-row";
@@ -8249,7 +8475,7 @@ var mountDAW = (target, options = {}) => {
8249
8475
  lyricDiv.style.alignItems = "stretch";
8250
8476
  lyricDiv.innerHTML = `
8251
8477
  <div class="dtm-row">
8252
- <span class="dtm-label">\u266A \u6B4C\u8A5E</span>
8478
+ <span class="dtm-label">\u266A UTAU</span>
8253
8479
  <select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
8254
8480
  <img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
8255
8481
  <select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
@@ -8323,7 +8549,7 @@ var mountDAW = (target, options = {}) => {
8323
8549
  o.textContent = label;
8324
8550
  lyricModelSel.appendChild(o);
8325
8551
  };
8326
- addOpt("", "\u306A\u3057");
8552
+ addOpt("", "\u30DC\u30FC\u30AB\u30EB\u306A\u3057");
8327
8553
  for (const m of LYRIC_MODELS) addOpt(m, lyricModelLabel(m));
8328
8554
  if (active.lyricModel && !LYRIC_MODELS.includes(active.lyricModel)) {
8329
8555
  addOpt(active.lyricModel, lyricModelLabel(active.lyricModel));
@@ -8372,21 +8598,27 @@ var mountDAW = (target, options = {}) => {
8372
8598
  lyricModelSel.addEventListener("change", () => {
8373
8599
  active.lyricModel = lyricModelSel.value;
8374
8600
  syncLyricVisibility();
8601
+ syncInstDisabled();
8602
+ fireLyricsChange(active);
8375
8603
  });
8376
8604
  lyricOctaveSel.addEventListener("change", () => {
8377
8605
  active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
8606
+ fireLyricsChange(active);
8378
8607
  });
8379
8608
  lyricInput.addEventListener("input", () => {
8380
8609
  active.lyrics = lyricInput.value;
8381
8610
  updateLyricCount();
8611
+ fireLyricsChange(active);
8382
8612
  });
8383
8613
  lyricVol.addEventListener("input", () => {
8384
8614
  active.vocalVolume = Number.parseInt(lyricVol.value, 10);
8385
8615
  lyricVolLabel.textContent = lyricVol.value;
8616
+ fireLyricsChange(active);
8386
8617
  });
8387
8618
  lyricPan.addEventListener("input", () => {
8388
8619
  active.vocalPan = Number.parseInt(lyricPan.value, 10);
8389
8620
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
8621
+ fireLyricsChange(active);
8390
8622
  });
8391
8623
  lyricPanLabel.style.cursor = "pointer";
8392
8624
  lyricPanLabel.title = "\u30BF\u30C3\u30D7\u3067\u4E2D\u592E(C)\u3078";
@@ -8394,6 +8626,7 @@ var mountDAW = (target, options = {}) => {
8394
8626
  active.vocalPan = 64;
8395
8627
  lyricPan.value = "64";
8396
8628
  lyricPanLabel.textContent = fmtPan(64);
8629
+ fireLyricsChange(active);
8397
8630
  });
8398
8631
  }
8399
8632
  if (active.config.id === "chord" && showChord) {
@@ -8468,13 +8701,19 @@ var mountDAW = (target, options = {}) => {
8468
8701
  const barLimitBars = Number(refs.barLimitSelect.value);
8469
8702
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
8470
8703
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
8704
+ const trackInstrumentsForMeta = {};
8705
+ trackStates.forEach((t, i) => {
8706
+ if (t.trackInstrument) trackInstrumentsForMeta[i] = t.trackInstrument;
8707
+ });
8708
+ const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
8471
8709
  const metaLineFull = formatMmlMeta(
8472
8710
  {
8473
8711
  instrument: currentInstrument || void 0,
8474
8712
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8475
8713
  volume: masterVolume,
8476
8714
  drumVolume,
8477
- mode
8715
+ mode,
8716
+ trackInstruments: trackInstMeta
8478
8717
  },
8479
8718
  " "
8480
8719
  );
@@ -8484,7 +8723,8 @@ var mountDAW = (target, options = {}) => {
8484
8723
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8485
8724
  volume: masterVolume,
8486
8725
  drumVolume,
8487
- mode
8726
+ mode,
8727
+ trackInstruments: trackInstMeta
8488
8728
  },
8489
8729
  ""
8490
8730
  );
@@ -8642,6 +8882,13 @@ var mountDAW = (target, options = {}) => {
8642
8882
  refs.drumVolume.value = String(meta.drumVolume);
8643
8883
  refs.drumVolumeLabel.textContent = `${meta.drumVolume}%`;
8644
8884
  }
8885
+ trackStates.forEach((t, i) => {
8886
+ const name = normalizeInstrumentName(meta.trackInstruments?.[i] ?? "");
8887
+ if (t.trackInstrument !== name) {
8888
+ t.trackInstrument = name;
8889
+ options.onTrackInstrumentChange?.(i, name);
8890
+ }
8891
+ });
8645
8892
  for (const t of trackStates) {
8646
8893
  t.lyrics = "";
8647
8894
  t.lyricModel = "";
@@ -8801,6 +9048,31 @@ var mountDAW = (target, options = {}) => {
8801
9048
  setLoading(false);
8802
9049
  }, 30);
8803
9050
  };
9051
+ const showConfirmModal = (message) => new Promise((resolve) => {
9052
+ const overlay = document.createElement("div");
9053
+ overlay.className = "dtm-modal-overlay";
9054
+ overlay.innerHTML = `
9055
+ <div class="dtm-modal">
9056
+ <div class="dtm-modal-header">
9057
+ <span class="dtm-modal-title">\u30E2\u30FC\u30C9\u306E\u78BA\u8A8D</span>
9058
+ </div>
9059
+ <div class="dtm-modal-body"><p>${message}</p></div>
9060
+ <div class="dtm-confirm-footer">
9061
+ <button class="dtm-btn dtm-btn--ghost dtm-confirm-no">\u3044\u3044\u3048\uFF08\u3053\u306E\u307E\u307E\u8AAD\u307F\u8FBC\u3080\uFF09</button>
9062
+ <button class="dtm-btn dtm-btn--primary dtm-confirm-yes">\u306F\u3044\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u308B\uFF09</button>
9063
+ </div>
9064
+ </div>`;
9065
+ const close = (result) => {
9066
+ overlay.remove();
9067
+ resolve(result);
9068
+ };
9069
+ overlay.querySelector(".dtm-confirm-yes").addEventListener("click", () => close(true));
9070
+ overlay.querySelector(".dtm-confirm-no").addEventListener("click", () => close(false));
9071
+ overlay.addEventListener("click", (e) => {
9072
+ if (e.target === overlay) close(false);
9073
+ });
9074
+ document.body.appendChild(overlay);
9075
+ });
8804
9076
  const wireEvents = () => {
8805
9077
  refs.playBtn.addEventListener("click", togglePlay);
8806
9078
  refs.playBtn.disabled = false;
@@ -8920,10 +9192,25 @@ var mountDAW = (target, options = {}) => {
8920
9192
  "click",
8921
9193
  () => copy(refs.outputMini.textContent ?? "", refs.copyMiniBtn)
8922
9194
  );
8923
- refs.mmlLoadBtn.addEventListener(
8924
- "click",
8925
- () => overlayDuring(() => loadMML(refs.mmlInput.value))
8926
- );
9195
+ refs.mmlLoadBtn.addEventListener("click", async () => {
9196
+ const mml = refs.mmlInput.value;
9197
+ if (!isAdvanced && options.onRequestAdvancedMode) {
9198
+ const { mergedTrackCount } = parseMML(mml, {
9199
+ stepsPerBar: renderConfig.stepsPerBar,
9200
+ clampTrackCount: trackStates.length
9201
+ });
9202
+ if (mergedTrackCount > 0) {
9203
+ const confirmed = await showConfirmModal(
9204
+ "\u521D\u5FC3\u8005\u30E2\u30FC\u30C9\u3067\u8AAD\u307F\u8FBC\u3080\u3068\u3001\u97F3\u304C\u5D29\u308C\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002<br>\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u307E\u3059\u304B\uFF1F"
9205
+ );
9206
+ if (confirmed) {
9207
+ options.onRequestAdvancedMode(mml);
9208
+ return;
9209
+ }
9210
+ }
9211
+ }
9212
+ overlayDuring(() => loadMML(mml));
9213
+ });
8927
9214
  let activeSamplePlayer = null;
8928
9215
  let activeSampleButton = null;
8929
9216
  const collapseActiveSample = () => {
@@ -9016,6 +9303,7 @@ var mountDAW = (target, options = {}) => {
9016
9303
  singingVoices: options.singingVoices,
9017
9304
  drumPatterns: options.drumPatterns,
9018
9305
  volume: masterVolume,
9306
+ _skipInfoModals: true,
9019
9307
  onStop: () => {
9020
9308
  if (activeSampleButton === htmlBtn) {
9021
9309
  htmlBtn.textContent = "\u25B6 \u8A66\u8074";
@@ -9097,7 +9385,7 @@ var mountDAW = (target, options = {}) => {
9097
9385
  refs.overlay.hidden = true;
9098
9386
  setLoading(false);
9099
9387
  });
9100
- refs.midiLoadBtn.addEventListener("click", () => {
9388
+ refs.midiLoadBtn.addEventListener("click", async () => {
9101
9389
  if (!pendingMidi) return;
9102
9390
  const selected = [];
9103
9391
  const btns = refs.midiTrackSelection.querySelectorAll("button");
@@ -9106,6 +9394,19 @@ var mountDAW = (target, options = {}) => {
9106
9394
  selected.push(detectedTracks[i].index);
9107
9395
  });
9108
9396
  if (selected.length === 0) return;
9397
+ if (!isAdvanced && options.onRequestAdvancedMode && selected.length > trackStates.length) {
9398
+ const confirmed = await showConfirmModal(
9399
+ "\u521D\u5FC3\u8005\u30E2\u30FC\u30C9\u3067\u8AAD\u307F\u8FBC\u3080\u3068\u3001\u97F3\u304C\u5D29\u308C\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002<br>\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u307E\u3059\u304B\uFF1F"
9400
+ );
9401
+ if (confirmed) {
9402
+ const midi = pendingMidi;
9403
+ const sel = selected.slice();
9404
+ options.onRequestAdvancedMode(void 0, (newDaw) => {
9405
+ newDaw.applyMidiParsed?.(midi, sel);
9406
+ });
9407
+ return;
9408
+ }
9409
+ }
9109
9410
  overlayDuring(() => applyMidiSelection(pendingMidi, selected));
9110
9411
  });
9111
9412
  };
@@ -9242,6 +9543,9 @@ var mountDAW = (target, options = {}) => {
9242
9543
  },
9243
9544
  loadMML,
9244
9545
  loadMIDI,
9546
+ applyMidiParsed: (midi, selectedIndices) => {
9547
+ overlayDuring(() => applyMidiSelection(midi, selectedIndices));
9548
+ },
9245
9549
  exportMIDI: exportMIDI2,
9246
9550
  setBpm,
9247
9551
  getPlaybackState: () => playbackState,
@@ -9276,6 +9580,37 @@ var mountDAW = (target, options = {}) => {
9276
9580
  if (audible) audioMutedTracks.delete(trackId);
9277
9581
  else audioMutedTracks.add(trackId);
9278
9582
  },
9583
+ applyLyrics: (trackId, data) => {
9584
+ const t = trackStates.find((s) => s.config.id === trackId);
9585
+ if (!t) return;
9586
+ t.lyrics = data.lyrics;
9587
+ t.lyricModel = data.model;
9588
+ t.vocalVolume = data.vocalVolume;
9589
+ t.vocalGate = data.vocalGate;
9590
+ t.vocalPan = data.vocalPan;
9591
+ t.vocalOctave = data.vocalOctave;
9592
+ },
9593
+ applyTrackInstrument: (trackIndex, instrumentName) => {
9594
+ const t = trackStates[trackIndex];
9595
+ if (!t) return;
9596
+ const name = normalizeInstrumentName(instrumentName);
9597
+ t.trackInstrument = name;
9598
+ if (t.config.id === activeTrackId) updateTrackPanel();
9599
+ },
9600
+ noteToCanvas: (step, pitch) => {
9601
+ const canvas = getGridCanvas();
9602
+ const x = step * renderConfig.stepWidth - currentOffsetX;
9603
+ const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
9604
+ const onScreen = x >= 0 && x <= canvas.width && y >= 0 && y <= canvas.height;
9605
+ let side = null;
9606
+ if (!onScreen) {
9607
+ if (x < 0) side = "left";
9608
+ else if (x > canvas.width) side = "right";
9609
+ else if (y < 0) side = "top";
9610
+ else side = "bottom";
9611
+ }
9612
+ return { x, y, onScreen, side };
9613
+ },
9279
9614
  destroy: () => {
9280
9615
  sequencer.stop();
9281
9616
  options.singingVoices?.stopStream();
@@ -9926,7 +10261,7 @@ var SoundFont = class _SoundFont {
9926
10261
  const { buffer, _param } = zone;
9927
10262
  if (!buffer || !_param) return;
9928
10263
  src.buffer = buffer;
9929
- g.gain.value = volume;
10264
+ g.gain.setValueAtTime(volume, _when);
9930
10265
  src.playbackRate.setValueAtTime(_param.playbackRate, 0);
9931
10266
  Object.assign(src, _param.src);
9932
10267
  const _duration = duration + _SoundFont.afterTime;
@@ -10194,6 +10529,14 @@ var createDtmStudio = async (options = {}) => {
10194
10529
  }
10195
10530
  })();
10196
10531
  let nameToKey = {};
10532
+ const resolveNameToKey = (name) => {
10533
+ if (nameToKey[name]) return nameToKey[name];
10534
+ const stripped = name.replace(/\s+/g, "").toLowerCase();
10535
+ const canonical = Object.keys(nameToKey).find(
10536
+ (k) => k.replace(/\s+/g, "").toLowerCase() === stripped
10537
+ );
10538
+ return canonical ? nameToKey[canonical] : void 0;
10539
+ };
10197
10540
  const soundFonts = /* @__PURE__ */ new Map();
10198
10541
  const loadingByKey = /* @__PURE__ */ new Map();
10199
10542
  const loadInstrument = (instrumentKey) => {
@@ -10279,7 +10622,7 @@ var createDtmStudio = async (options = {}) => {
10279
10622
  if (opts.label !== null) {
10280
10623
  const lab = doc.createElement("span");
10281
10624
  lab.className = "dtm-controlbar-label";
10282
- lab.textContent = opts.label ?? "INSTRUMENT";
10625
+ lab.textContent = opts.label ?? "\u697D\u5668\u30D7\u30EA\u30BB\u30C3\u30C8";
10283
10626
  wrapper.appendChild(lab);
10284
10627
  }
10285
10628
  const select = doc.createElement("select");
@@ -10344,7 +10687,13 @@ var createDtmStudio = async (options = {}) => {
10344
10687
  const mountedPlayers = [];
10345
10688
  const mountedModeSwitches = [];
10346
10689
  const mountEditor = (target, opts = {}) => {
10347
- const { preset, presetUI, onInstrumentChange, ...dawOverrides } = opts;
10690
+ const {
10691
+ preset,
10692
+ presetUI,
10693
+ onInstrumentChange,
10694
+ onTrackInstrumentChange: externalOnTrackInstrumentChange,
10695
+ ...dawOverrides
10696
+ } = opts;
10348
10697
  const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
10349
10698
  const trackIds = tracks.map((t) => t.id);
10350
10699
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
@@ -10352,14 +10701,29 @@ var createDtmStudio = async (options = {}) => {
10352
10701
  const initialPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : presetKey;
10353
10702
  let editorPreset = initialPreset;
10354
10703
  const isAdvancedMode = dawOverrides.mode === "advanced";
10704
+ const trackInstOverrides = /* @__PURE__ */ new Map();
10705
+ if (meta.trackInstruments) {
10706
+ for (const [idxStr, name] of Object.entries(meta.trackInstruments)) {
10707
+ const idx = Number(idxStr);
10708
+ const key = resolveNameToKey(name);
10709
+ if (key) trackInstOverrides.set(idx, key);
10710
+ }
10711
+ }
10355
10712
  const playNote = (e) => {
10356
- const sf2 = resolveSoundFont(
10357
- editorPreset,
10358
- e.trackId,
10359
- isAdvancedMode ? "advanced" : "simple"
10360
- );
10361
- if (!sf2) return;
10362
- sf2.play({
10713
+ const trackIdx = tracks.findIndex((t) => t.id === e.trackId);
10714
+ const overrideKey = trackIdx >= 0 ? trackInstOverrides.get(trackIdx) : void 0;
10715
+ let sfInst;
10716
+ if (overrideKey) {
10717
+ sfInst = soundFonts.get(overrideKey);
10718
+ } else {
10719
+ sfInst = resolveSoundFont(
10720
+ editorPreset,
10721
+ e.trackId,
10722
+ isAdvancedMode ? "advanced" : "simple"
10723
+ );
10724
+ }
10725
+ if (!sfInst) return;
10726
+ sfInst.play({
10363
10727
  ctx: audioCtx,
10364
10728
  destination: masterGain,
10365
10729
  pitch: e.pitch,
@@ -10376,6 +10740,17 @@ var createDtmStudio = async (options = {}) => {
10376
10740
  }
10377
10741
  onInstrumentChange?.(key);
10378
10742
  };
10743
+ const handleTrackInstrumentChange = async (trackIndex, instrumentName) => {
10744
+ if (!instrumentName) {
10745
+ trackInstOverrides.delete(trackIndex);
10746
+ return;
10747
+ }
10748
+ await listReady;
10749
+ const key = resolveNameToKey(instrumentName);
10750
+ if (!key) return;
10751
+ trackInstOverrides.set(trackIndex, key);
10752
+ await loadInstrument(key);
10753
+ };
10379
10754
  const base = {
10380
10755
  getAudioTime: () => audioCtx.currentTime,
10381
10756
  onResumeAudio: resumeAudio,
@@ -10384,6 +10759,10 @@ var createDtmStudio = async (options = {}) => {
10384
10759
  singingVoices,
10385
10760
  parseMidi,
10386
10761
  onInstrumentChange: handleInstrumentChange,
10762
+ onTrackInstrumentChange: (idx, name) => {
10763
+ void handleTrackInstrumentChange(idx, name);
10764
+ externalOnTrackInstrumentChange?.(idx, name);
10765
+ },
10387
10766
  ...dawOverrides
10388
10767
  };
10389
10768
  const daw = mountDAW(target, base);
@@ -10431,6 +10810,10 @@ var createDtmStudio = async (options = {}) => {
10431
10810
  presetSelect.setValue(name);
10432
10811
  }
10433
10812
  },
10813
+ applyTrackInstrument: (trackIndex, instrumentName) => {
10814
+ daw.applyTrackInstrument(trackIndex, instrumentName);
10815
+ void handleTrackInstrumentChange(trackIndex, instrumentName);
10816
+ },
10434
10817
  destroy
10435
10818
  };
10436
10819
  };
@@ -10438,8 +10821,8 @@ var createDtmStudio = async (options = {}) => {
10438
10821
  const doc = target.ownerDocument;
10439
10822
  const tracksFor = opts.tracksFor ?? ((m) => m === "advanced" ? TRACKS_ADVANCED : TRACKS_SIMPLE);
10440
10823
  const labels = {
10441
- simple: opts.labels?.simple ?? "\u30B7\u30F3\u30D7\u30EB",
10442
- advanced: opts.labels?.advanced ?? "\u30A2\u30C9\u30D0\u30F3\u30B9"
10824
+ simple: opts.labels?.simple ?? "\u521D\u5FC3\u8005",
10825
+ advanced: opts.labels?.advanced ?? "\u4E0A\u7D1A\u8005"
10443
10826
  };
10444
10827
  const editorOptionsFor = (mode) => typeof opts.editorOptions === "function" ? opts.editorOptions(mode) : opts.editorOptions ?? {};
10445
10828
  let currentMode = opts.mode ?? "simple";
@@ -10449,7 +10832,7 @@ var createDtmStudio = async (options = {}) => {
10449
10832
  if (opts.label !== null) {
10450
10833
  const lab = doc.createElement("span");
10451
10834
  lab.className = "dtm-controlbar-label";
10452
- lab.textContent = opts.label ?? "MODE";
10835
+ lab.textContent = opts.label ?? "\u30E2\u30FC\u30C9";
10453
10836
  wrapper.appendChild(lab);
10454
10837
  }
10455
10838
  const seg = doc.createElement("div");
@@ -10480,7 +10863,15 @@ var createDtmStudio = async (options = {}) => {
10480
10863
  ...editorOpts,
10481
10864
  mode,
10482
10865
  tracks: tracksFor(mode),
10483
- initialMML: mml ?? editorOpts.initialMML
10866
+ initialMML: mml ?? editorOpts.initialMML,
10867
+ onRequestAdvancedMode: (pendingMml, applyMidi) => {
10868
+ doUnmount();
10869
+ currentMode = "advanced";
10870
+ updateButtons();
10871
+ opts.onChange?.("advanced");
10872
+ doMount("advanced", pendingMml);
10873
+ if (applyMidi && daw) applyMidi(daw);
10874
+ }
10484
10875
  });
10485
10876
  attachWrapper();
10486
10877
  opts.onMount?.(daw, mode);
@@ -10530,24 +10921,44 @@ var createDtmStudio = async (options = {}) => {
10530
10921
  (idx) => getRoleForTrackIndex(idx, isAdvancedMode ? "advanced" : "simple")
10531
10922
  );
10532
10923
  const loadTrackIds = trackIds.length > 0 ? trackIds : [...TRACK_ROLES];
10533
- void loadPreset(
10534
- playerPreset,
10535
- loadTrackIds,
10536
- isAdvancedMode ? "advanced" : "simple"
10537
- );
10538
- const playPlayerNote = (e) => {
10539
- const idx = Number(e.trackId);
10540
- const role = getRoleForTrackIndex(
10541
- idx,
10542
- isAdvancedMode ? "advanced" : "simple"
10543
- );
10544
- const sf2 = resolveSoundFont(
10924
+ const playerTrackInstKeys = /* @__PURE__ */ new Map();
10925
+ const loadPlayerTrackInstruments = async () => {
10926
+ if (!meta.trackInstruments) return;
10927
+ await listReady;
10928
+ for (const [idxStr, name] of Object.entries(meta.trackInstruments)) {
10929
+ const key = resolveNameToKey(name);
10930
+ if (!key) continue;
10931
+ playerTrackInstKeys.set(Number(idxStr), key);
10932
+ await loadInstrument(key);
10933
+ }
10934
+ };
10935
+ void Promise.all([
10936
+ loadPreset(
10545
10937
  playerPreset,
10546
- role,
10938
+ loadTrackIds,
10547
10939
  isAdvancedMode ? "advanced" : "simple"
10548
- );
10549
- if (!sf2) return;
10550
- sf2.play({
10940
+ ),
10941
+ loadPlayerTrackInstruments()
10942
+ ]);
10943
+ const playPlayerNote = (e) => {
10944
+ const idx = Number(e.trackId);
10945
+ const overrideKey = playerTrackInstKeys.get(idx);
10946
+ let sfInst;
10947
+ if (overrideKey) {
10948
+ sfInst = soundFonts.get(overrideKey);
10949
+ } else {
10950
+ const role = getRoleForTrackIndex(
10951
+ idx,
10952
+ isAdvancedMode ? "advanced" : "simple"
10953
+ );
10954
+ sfInst = resolveSoundFont(
10955
+ playerPreset,
10956
+ role,
10957
+ isAdvancedMode ? "advanced" : "simple"
10958
+ );
10959
+ }
10960
+ if (!sfInst) return;
10961
+ sfInst.play({
10551
10962
  ctx: audioCtx,
10552
10963
  destination: masterGain,
10553
10964
  pitch: e.pitch,
@@ -10605,6 +11016,7 @@ export {
10605
11016
  DRUM_FONT,
10606
11017
  DRUM_KEYS,
10607
11018
  DRUM_PATTERNS,
11019
+ GM_INSTRUMENT_NAMES,
10608
11020
  INSTRUMENT_PRESETS,
10609
11021
  KOE_BASE_URL,
10610
11022
  KOE_VOICEBANKS,
@@ -10648,7 +11060,6 @@ export {
10648
11060
  exportMIDI,
10649
11061
  extractMidiPlacements,
10650
11062
  extractMidiPlacementsByTrack,
10651
- fetchSoundFontList,
10652
11063
  formatMmlMeta,
10653
11064
  freqFromPitch,
10654
11065
  generateRandomPattern,