@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.js CHANGED
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  DRUM_FONT: () => DRUM_FONT,
32
32
  DRUM_KEYS: () => DRUM_KEYS,
33
33
  DRUM_PATTERNS: () => DRUM_PATTERNS,
34
+ GM_INSTRUMENT_NAMES: () => GM_INSTRUMENT_NAMES,
34
35
  INSTRUMENT_PRESETS: () => INSTRUMENT_PRESETS,
35
36
  KOE_BASE_URL: () => KOE_BASE_URL,
36
37
  KOE_VOICEBANKS: () => KOE_VOICEBANKS,
@@ -74,7 +75,6 @@ __export(index_exports, {
74
75
  exportMIDI: () => exportMIDI,
75
76
  extractMidiPlacements: () => extractMidiPlacements,
76
77
  extractMidiPlacementsByTrack: () => extractMidiPlacementsByTrack,
77
- fetchSoundFontList: () => fetchSoundFontList,
78
78
  formatMmlMeta: () => formatMmlMeta,
79
79
  freqFromPitch: () => freqFromPitch,
80
80
  generateRandomPattern: () => generateRandomPattern,
@@ -118,25 +118,146 @@ function createAudioContext() {
118
118
  drumGainNode.connect(audioCtx.destination);
119
119
  return { audioCtx, gainNode, drumGainNode };
120
120
  }
121
- async function fetchSoundFontList(ttl) {
122
- const res = await fetch(`https://rpgen3.github.io/soundfont/list/${ttl}.txt`);
123
- const str = await res.text();
124
- return str.trim().split("\n");
125
- }
121
+ var FONT_NAME_SURIKOV = `0000 Acoustic Grand Piano
122
+ 0010 Bright Acoustic Piano
123
+ 0020 Electric Grand Piano
124
+ 0030 Honky-tonk Piano
125
+ 0040 Electric Piano 1
126
+ 0050 Electric Piano 2
127
+ 0060 Harpsichord
128
+ 0070 Clavinet
129
+ 0080 Celesta
130
+ 0090 Glockenspiel
131
+ 0100 Music Box
132
+ 0110 Vibraphone
133
+ 0120 Marimba
134
+ 0130 Xylophone
135
+ 0140 Tubular Bells
136
+ 0150 Dulcimer
137
+ 0160 Drawbar Organ
138
+ 0170 Percussive Organ
139
+ 0180 Rock Organ
140
+ 0190 Church Organ
141
+ 0200 Reed Organ
142
+ 0210 Accordion
143
+ 0220 Harmonica
144
+ 0230 Tango Accordion
145
+ 0240 Acoustic Guitar (nylon)
146
+ 0250 Acoustic Guitar (steel)
147
+ 0260 Electric Guitar (jazz)
148
+ 0270 Electric Guitar (clean)
149
+ 0280 Electric Guitar (muted)
150
+ 0290 Overdriven Guitar
151
+ 0300 Distortion Guitar
152
+ 0310 Guitar Harmonics
153
+ 0320 Acoustic Bass
154
+ 0330 Electric Bass (finger)
155
+ 0340 Electric Bass (pick)
156
+ 0350 Fretless Bass
157
+ 0360 Slap Bass 1
158
+ 0370 Slap Bass 2
159
+ 0380 Synth Bass 1
160
+ 0390 Synth Bass 2
161
+ 0400 Violin
162
+ 0410 Viola
163
+ 0420 Cello
164
+ 0430 Contrabass
165
+ 0440 Tremolo Strings
166
+ 0450 Pizzicato Strings
167
+ 0460 Orchestral Harp
168
+ 0470 Timpani
169
+ 0480 String Ensemble 1
170
+ 0490 String Ensemble 2
171
+ 0500 Synth Strings 1
172
+ 0510 Synth Strings 2
173
+ 0520 Choir Aahs
174
+ 0530 Voice Oohs
175
+ 0540 Synth Choir
176
+ 0550 Orchestra Hit
177
+ 0560 Trumpet
178
+ 0570 Trombone
179
+ 0580 Tuba
180
+ 0590 Muted Trumpet
181
+ 0600 French Horn
182
+ 0610 Brass Section
183
+ 0620 Synth Brass 1
184
+ 0630 Synth Brass 2
185
+ 0640 Soprano Sax
186
+ 0650 Alto Sax
187
+ 0660 Tenor Sax
188
+ 0670 Baritone Sax
189
+ 0680 Oboe
190
+ 0690 English Horn
191
+ 0700 Bassoon
192
+ 0710 Clarinet
193
+ 0720 Piccolo
194
+ 0730 Flute
195
+ 0740 Recorder
196
+ 0750 Pan Flute
197
+ 0760 Blown bottle
198
+ 0770 Shakuhachi
199
+ 0780 Whistle
200
+ 0790 Ocarina
201
+ 0800 Lead 1 (square)
202
+ 0810 Lead 2 (sawtooth)
203
+ 0820 Lead 3 (calliope)
204
+ 0830 Lead 4 (chiff)
205
+ 0840 Lead 5 (charang)
206
+ 0850 Lead 6 (voice)
207
+ 0860 Lead 7 (fifths)
208
+ 0870 Lead 8 (bass + lead)
209
+ 0880 Pad 1 (new age)
210
+ 0890 Pad 2 (warm)
211
+ 0900 Pad 3 (polysynth)
212
+ 0910 Pad 4 (choir)
213
+ 0920 Pad 5 (bowed)
214
+ 0930 Pad 6 (metallic)
215
+ 0940 Pad 7 (halo)
216
+ 0950 Pad 8 (sweep)
217
+ 0960 FX 1 (rain)
218
+ 0970 FX 2 (soundtrack)
219
+ 0980 FX 3 (crystal)
220
+ 0990 FX 4 (atmosphere)
221
+ 1000 FX 5 (brightness)
222
+ 1010 FX 6 (goblins)
223
+ 1020 FX 7 (echoes)
224
+ 1030 FX 8 (sci-fi)
225
+ 1040 Sitar
226
+ 1050 Banjo
227
+ 1060 Shamisen
228
+ 1070 Koto
229
+ 1080 Kalimba
230
+ 1090 Bagpipe
231
+ 1100 Fiddle
232
+ 1110 Shanai
233
+ 1120 Tinkle Bell
234
+ 1130 Agogo
235
+ 1140 Steel Drums
236
+ 1150 Woodblock
237
+ 1160 Taiko Drum
238
+ 1170 Melodic Tom
239
+ 1180 Synth Drum
240
+ 1190 Reverse Cymbal
241
+ 1200 Guitar Fret Noise
242
+ 1210 Breath Noise
243
+ 1220 Seashore
244
+ 1230 Bird Tweet
245
+ 1240 Telephone Ring
246
+ 1250 Helicopter
247
+ 1260 Applause
248
+ 1270 Gunshot`;
126
249
  async function buildNameToKeyMapping() {
127
250
  const nameToKey = {};
128
- try {
129
- const fontNames = await fetchSoundFontList("fontName_surikov");
130
- fontNames.forEach((line) => {
131
- const [key, ...nameParts] = line.split(" ");
132
- const name = nameParts.join(" ");
133
- nameToKey[name] = key;
134
- });
135
- } catch (e) {
136
- console.error("Failed to build name-to-key mapping:", e);
251
+ for (const line of FONT_NAME_SURIKOV.trim().split("\n")) {
252
+ const spaceIdx = line.indexOf(" ");
253
+ if (spaceIdx === -1) continue;
254
+ const key = line.slice(0, spaceIdx);
255
+ const name = line.slice(spaceIdx + 1);
256
+ nameToKey[name] = key;
137
257
  }
138
258
  return nameToKey;
139
259
  }
260
+ var GM_INSTRUMENT_NAMES = FONT_NAME_SURIKOV.trim().split("\n").map((line) => line.slice(line.indexOf(" ") + 1));
140
261
 
141
262
  // node_modules/.pnpm/@onjmin+chord-parser@1.0.2/node_modules/@onjmin/chord-parser/dist/index.mjs
142
263
  var SHARP_NAMES = [
@@ -1214,14 +1335,17 @@ var buildUI = (target, options) => {
1214
1335
  target.innerHTML = `
1215
1336
  <div class="dtm-daw" data-dtm="root">
1216
1337
  <div class="dtm-topbar" data-dtm="transport">
1217
- <button class="dtm-iconbtn" data-dtm="prev-bar" title="1\u5C0F\u7BC0\u524D">${icon("chevronLeft")}</button>
1218
- <button class="dtm-play" data-dtm="play" disabled>${icon("play")}<span>\u8A66\u8074</span></button>
1219
- <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
1220
- <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
1221
- <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
1222
- <span class="dtm-grow"></span>
1223
- <span class="dtm-label">BPM</span>
1224
- <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
1338
+ <div class="dtm-topbar-row1">
1339
+ <button class="dtm-iconbtn" data-dtm="prev-bar" title="1\u5C0F\u7BC0\u524D">${icon("chevronLeft")}</button>
1340
+ <button class="dtm-play" data-dtm="play" disabled>${icon("play")}<span>\u8A66\u8074</span></button>
1341
+ <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
1342
+ <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
1343
+ <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
1344
+ <span class="dtm-grow"></span>
1345
+ <span class="dtm-label">BPM</span>
1346
+ <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
1347
+ </div>
1348
+ <div class="dtm-tracks" data-dtm="track-tabs"></div>
1225
1349
  </div>
1226
1350
 
1227
1351
  <div class="dtm-tooldock">
@@ -1244,8 +1368,6 @@ var buildUI = (target, options) => {
1244
1368
  </select>
1245
1369
  </div>
1246
1370
 
1247
- <div class="dtm-tracks" data-dtm="track-tabs"></div>
1248
-
1249
1371
  <div class="dtm-roll-wrap">
1250
1372
  <div class="dtm-roll" data-dtm="roll">
1251
1373
  <div data-dtm="wrapper" style="position:absolute;inset:0;"></div>
@@ -4276,6 +4398,7 @@ var PITCH_MAP2 = {
4276
4398
  };
4277
4399
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
4278
4400
  var META_DIRECTIVE = /#(inst|drum|volume|drumvolume|mode)=([\w-]+)/gi;
4401
+ var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
4279
4402
  var parseMmlMeta = (mml) => {
4280
4403
  const meta = {};
4281
4404
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -4294,9 +4417,17 @@ var parseMmlMeta = (mml) => {
4294
4417
  }
4295
4418
  }
4296
4419
  }
4420
+ for (const m of mml.matchAll(TRACK_INST_DIRECTIVE)) {
4421
+ const idx = Number.parseInt(m[1], 10);
4422
+ const name = m[2].trim();
4423
+ if (!Number.isNaN(idx) && name) {
4424
+ meta.trackInstruments ??= {};
4425
+ meta.trackInstruments[idx] = name;
4426
+ }
4427
+ }
4297
4428
  return meta;
4298
4429
  };
4299
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "");
4430
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "");
4300
4431
  var formatMmlMeta = (meta, space = "") => {
4301
4432
  const parts = [];
4302
4433
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -4305,6 +4436,11 @@ var formatMmlMeta = (meta, space = "") => {
4305
4436
  if (meta.drumVolume !== void 0)
4306
4437
  parts.push(`#drumvolume=${meta.drumVolume}`);
4307
4438
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
4439
+ if (meta.trackInstruments) {
4440
+ for (const [idx, name] of Object.entries(meta.trackInstruments)) {
4441
+ if (name) parts.push(`#t${idx}inst=${name}`);
4442
+ }
4443
+ }
4308
4444
  return parts.join(space);
4309
4445
  };
4310
4446
  var parseMML = (mml, options = {}) => {
@@ -4926,7 +5062,7 @@ var DAW_CSS = `
4926
5062
  top: 0;
4927
5063
  z-index: 20;
4928
5064
  display: flex;
4929
- flex-wrap: nowrap;
5065
+ flex-wrap: wrap;
4930
5066
  align-items: center;
4931
5067
  gap: var(--dtm-gap);
4932
5068
  padding: 6px;
@@ -4936,18 +5072,19 @@ var DAW_CSS = `
4936
5072
  inset 0 0 0 2px var(--c-black),
4937
5073
  0 0 0 2px var(--dtm-success),
4938
5074
  4px 4px 0 var(--c-black);
5075
+ }
5076
+ .dtm-topbar-row1 {
5077
+ display: flex;
5078
+ align-items: center;
5079
+ gap: var(--dtm-gap);
5080
+ flex-basis: 100%;
5081
+ min-width: 0;
4939
5082
  overflow-x: auto;
4940
5083
  scrollbar-width: none;
4941
5084
  }
4942
- .dtm-topbar::-webkit-scrollbar {
4943
- display: none;
4944
- }
4945
- .dtm-topbar > * {
4946
- flex-shrink: 0;
4947
- }
4948
- .dtm-topbar > .dtm-grow {
4949
- flex-shrink: 1;
4950
- }
5085
+ .dtm-topbar-row1::-webkit-scrollbar { display: none; }
5086
+ .dtm-topbar-row1 > * { flex-shrink: 0; }
5087
+ .dtm-topbar-row1 > .dtm-grow { flex-shrink: 1; }
4951
5088
 
4952
5089
  /* PLAY\u30DC\u30BF\u30F3 \u2014 \u30B2\u30FC\u30E0\u306E\u300C\u6C7A\u5B9A\u30DC\u30BF\u30F3\u300D\u7684\u5B58\u5728\u611F */
4953
5090
  .dtm-play {
@@ -5137,46 +5274,39 @@ var DAW_CSS = `
5137
5274
  .dtm-modebtn--active { background: var(--dtm-primary); color: var(--dtm-pfg); }
5138
5275
  .dtm-modebtn:not(.dtm-modebtn--active):active { background: var(--dtm-border2); }
5139
5276
 
5140
- /* \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 */
5277
+ /* \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 */
5141
5278
  .dtm-tracks {
5279
+ flex-basis: 100%;
5142
5280
  display: flex;
5143
- flex-wrap: wrap;
5144
- gap: var(--dtm-gap);
5281
+ flex-wrap: nowrap;
5282
+ gap: 3px;
5145
5283
  }
5146
5284
  .dtm-pill {
5147
5285
  --dtm-pill-color: var(--dtm-primary);
5148
5286
  display: inline-flex;
5149
5287
  align-items: center;
5150
- gap: 8px;
5151
- flex: 1 1 auto;
5152
5288
  justify-content: center;
5153
- min-height: 42px;
5154
- padding: 0 12px;
5155
- border: 2px solid var(--dtm-border2);
5156
- background: var(--dtm-deep);
5157
- color: var(--dtm-muted);
5289
+ flex: 1 1 0;
5290
+ min-width: 0;
5291
+ height: 26px;
5292
+ padding: 0;
5293
+ border: 2px solid var(--c-black);
5294
+ background: color-mix(in srgb, var(--dtm-pill-color) 40%, black);
5295
+ color: var(--c-white);
5158
5296
  font-family: var(--dtm-font);
5159
- font-size: 13px;
5160
- text-transform: uppercase;
5161
- letter-spacing: .1em;
5297
+ font-size: 11px;
5298
+ font-weight: bold;
5162
5299
  cursor: pointer;
5163
- box-shadow: 3px 3px 0 var(--c-black);
5164
- }
5165
- .dtm-pill .dtm-dot {
5166
- width: 8px; height: 8px;
5167
- background: var(--dtm-pill-color);
5168
- flex: 0 0 auto;
5169
- box-shadow: 1px 1px 0 var(--c-black);
5300
+ box-shadow: 2px 2px 0 var(--c-black);
5301
+ opacity: 0.7;
5170
5302
  }
5171
- /* \u30A2\u30AF\u30C6\u30A3\u30D6\u9078\u629E = \u91D1\u8272\u30CF\u30A4\u30E9\u30A4\u30C8 + \u30AB\u30FC\u30BD\u30EB */
5303
+ /* \u30A2\u30AF\u30C6\u30A3\u30D6\u9078\u629E = \u4E0D\u900F\u660E + \u91D1\u67A0 */
5172
5304
  .dtm-pill--active {
5305
+ opacity: 1;
5173
5306
  border-color: var(--dtm-gold);
5174
- color: var(--dtm-gold);
5175
- background: var(--dtm-surface);
5176
- box-shadow: 0 0 0 2px var(--dtm-gold), 3px 3px 0 var(--c-black);
5307
+ box-shadow: 0 0 0 1px var(--dtm-gold), 2px 2px 0 var(--c-black);
5177
5308
  }
5178
- .dtm-pill--active::before { content: "\u25BA "; font-size: 10px; }
5179
- .dtm-pill:not(.dtm-pill--active):active { transform: translate(3px,3px); box-shadow: none; }
5309
+ .dtm-pill:not(.dtm-pill--active):active { transform: translate(2px,2px); box-shadow: none; }
5180
5310
 
5181
5311
  /* \u2500\u2500\u2500 \u30D4\u30A2\u30CE\u30ED\u30FC\u30EB\uFF08\u30C8\u30E9\u30C3\u30AB\u30FC\u98A8\uFF09 \u2500\u2500\u2500 */
5182
5312
  .dtm-roll-wrap { display: flex; gap: var(--dtm-gap); }
@@ -5320,7 +5450,7 @@ var DAW_CSS = `
5320
5450
  }
5321
5451
  .dtm-overlay[hidden] { display: none; }
5322
5452
  .dtm-overlay::before {
5323
- content: 'NOW LOADING';
5453
+ content: '\u30ED\u30FC\u30C9\u4E2D';
5324
5454
  font-family: var(--dtm-font);
5325
5455
  font-size: 13px;
5326
5456
  color: var(--dtm-primary);
@@ -5513,6 +5643,15 @@ var DAW_CSS = `
5513
5643
  justify-content: center;
5514
5644
  }
5515
5645
 
5646
+ .dtm-confirm-footer {
5647
+ padding: 8px 12px;
5648
+ border-top: 2px solid var(--c-black);
5649
+ background: var(--dtm-deep);
5650
+ display: flex;
5651
+ gap: 8px;
5652
+ justify-content: flex-end;
5653
+ }
5654
+
5516
5655
  .dtm-modal {
5517
5656
  max-width: 500px;
5518
5657
  width: 100%;
@@ -6535,9 +6674,11 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6535
6674
  const mmlInfoItem = makeMenuItem("MML\u66F8\u5F0F\u3068\u306F");
6536
6675
  const embedItem = makeMenuItem("\u57CB\u3081\u8FBC\u3080");
6537
6676
  const copyMmlItem = makeMenuItem("MML\u30B3\u30D4\u30FC");
6538
- menuDropdown.appendChild(showMmlItem);
6539
- menuDropdown.appendChild(mmlInfoItem);
6540
- menuDropdown.appendChild(embedItem);
6677
+ if (!options._skipInfoModals) {
6678
+ menuDropdown.appendChild(showMmlItem);
6679
+ menuDropdown.appendChild(mmlInfoItem);
6680
+ menuDropdown.appendChild(embedItem);
6681
+ }
6541
6682
  menuDropdown.appendChild(copyMmlItem);
6542
6683
  menuContainer.appendChild(menuDropdown);
6543
6684
  mmlHeader.appendChild(menuContainer);
@@ -6691,6 +6832,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6691
6832
  volume: trackVolume,
6692
6833
  // 解説モーダル内の試聴サンプルは規約同意を要求しない。
6693
6834
  skipConsent: true,
6835
+ // 再帰的なモーダル生成を防ぐ。
6836
+ _skipInfoModals: true,
6694
6837
  onStop: () => {
6695
6838
  if (activeSampleBtn === el) resetSampleBtn(el);
6696
6839
  }
@@ -6700,65 +6843,67 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6700
6843
  });
6701
6844
  }
6702
6845
  };
6703
- showMmlItem.addEventListener("click", (e) => {
6704
- e.stopPropagation();
6705
- toggleMenu(false);
6706
- const modalBody = openInfoModal("MML\u3092\u8868\u793A");
6707
- const desc = doc.createElement("p");
6708
- 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";
6709
- desc.style.marginBottom = "8px";
6710
- modalBody.appendChild(desc);
6711
- const sourceMml = options.getMml?.() ?? mml;
6712
- const displayMml = sourceMml.split(";").map((s) => s.trim()).filter((s) => s.length > 0).join(";\n");
6713
- const pre = doc.createElement("pre");
6714
- pre.textContent = displayMml;
6715
- pre.style.whiteSpace = "pre-wrap";
6716
- pre.style.wordBreak = "break-all";
6717
- pre.style.cursor = "text";
6718
- pre.addEventListener("click", () => {
6719
- const range = doc.createRange();
6720
- range.selectNodeContents(pre);
6721
- const sel = doc.defaultView?.getSelection();
6722
- sel?.removeAllRanges();
6723
- sel?.addRange(range);
6724
- });
6725
- modalBody.appendChild(pre);
6726
- appendCopyButton(modalBody, sourceMml);
6727
- });
6728
- mmlInfoItem.addEventListener("click", (e) => {
6729
- e.stopPropagation();
6730
- toggleMenu(false);
6731
- const modalBody = openInfoModal("MML\u306E\u66F8\u304D\u65B9\u89E3\u8AAC");
6732
- modalBody.innerHTML = MML_INFO_HTML;
6733
- wireSampleButtons(modalBody);
6734
- });
6735
- embedItem.addEventListener("click", async (e) => {
6736
- e.stopPropagation();
6737
- toggleMenu(false);
6738
- const modalBody = openInfoModal("\u57CB\u3081\u8FBC\u307F");
6739
- const loading = doc.createElement("p");
6740
- loading.textContent = "\u751F\u6210\u4E2D...";
6741
- modalBody.appendChild(loading);
6742
- try {
6743
- const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6744
- const payload = await encodeMml(mml);
6745
- const url2 = `${embedBase}#${payload}`;
6746
- const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6747
- if (!modalBody.isConnected) return;
6748
- loading.remove();
6846
+ if (!options._skipInfoModals) {
6847
+ showMmlItem.addEventListener("click", (e) => {
6848
+ e.stopPropagation();
6849
+ toggleMenu(false);
6850
+ const modalBody = openInfoModal("MML\u3092\u8868\u793A");
6749
6851
  const desc = doc.createElement("p");
6750
- 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";
6852
+ 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";
6853
+ desc.style.marginBottom = "8px";
6854
+ modalBody.appendChild(desc);
6855
+ const sourceMml = options.getMml?.() ?? mml;
6856
+ const displayMml = sourceMml.split(";").map((s) => s.trim()).filter((s) => s.length > 0).join(";\n");
6751
6857
  const pre = doc.createElement("pre");
6752
- pre.textContent = snippet;
6858
+ pre.textContent = displayMml;
6753
6859
  pre.style.whiteSpace = "pre-wrap";
6754
6860
  pre.style.wordBreak = "break-all";
6755
- modalBody.append(desc, pre);
6756
- appendCopyButton(modalBody, snippet);
6757
- } catch (err2) {
6758
- console.error("[dtm] failed to generate embed snippet", err2);
6759
- if (modalBody.isConnected) loading.textContent = "\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F";
6760
- }
6761
- });
6861
+ pre.style.cursor = "text";
6862
+ pre.addEventListener("click", () => {
6863
+ const range = doc.createRange();
6864
+ range.selectNodeContents(pre);
6865
+ const sel = doc.defaultView?.getSelection();
6866
+ sel?.removeAllRanges();
6867
+ sel?.addRange(range);
6868
+ });
6869
+ modalBody.appendChild(pre);
6870
+ appendCopyButton(modalBody, sourceMml);
6871
+ });
6872
+ mmlInfoItem.addEventListener("click", (e) => {
6873
+ e.stopPropagation();
6874
+ toggleMenu(false);
6875
+ const modalBody = openInfoModal("MML\u306E\u66F8\u304D\u65B9\u89E3\u8AAC");
6876
+ modalBody.innerHTML = MML_INFO_HTML;
6877
+ wireSampleButtons(modalBody);
6878
+ });
6879
+ embedItem.addEventListener("click", async (e) => {
6880
+ e.stopPropagation();
6881
+ toggleMenu(false);
6882
+ const modalBody = openInfoModal("\u57CB\u3081\u8FBC\u307F");
6883
+ const loading = doc.createElement("p");
6884
+ loading.textContent = "\u751F\u6210\u4E2D...";
6885
+ modalBody.appendChild(loading);
6886
+ try {
6887
+ const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6888
+ const payload = await encodeMml(mml);
6889
+ const url2 = `${embedBase}#${payload}`;
6890
+ const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6891
+ if (!modalBody.isConnected) return;
6892
+ loading.remove();
6893
+ const desc = doc.createElement("p");
6894
+ 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";
6895
+ const pre = doc.createElement("pre");
6896
+ pre.textContent = snippet;
6897
+ pre.style.whiteSpace = "pre-wrap";
6898
+ pre.style.wordBreak = "break-all";
6899
+ modalBody.append(desc, pre);
6900
+ appendCopyButton(modalBody, snippet);
6901
+ } catch (err2) {
6902
+ console.error("[dtm] failed to generate embed snippet", err2);
6903
+ if (modalBody.isConnected) loading.textContent = "\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F";
6904
+ }
6905
+ });
6906
+ }
6762
6907
  copyMmlItem.addEventListener("click", async (e) => {
6763
6908
  e.stopPropagation();
6764
6909
  const success = await copyToClipboard(doc, options.getMml?.() ?? mml);
@@ -7355,8 +7500,8 @@ var MIDI_INFO_HTML = `
7355
7500
 
7356
7501
  <h4>3. \u30E2\u30FC\u30C9\u306B\u3088\u308B\u53D6\u308A\u8FBC\u307F\u65B9\u306E\u9055\u3044</h4>
7357
7502
  <ul>
7358
- <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>
7359
- <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>
7503
+ <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>
7504
+ <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>
7360
7505
  </ul>
7361
7506
 
7362
7507
  <h4>4. MIDI\u30D5\u30A1\u30A4\u30EB\u3092\u624B\u306B\u5165\u308C\u308B</h4>
@@ -7408,105 +7553,105 @@ var TRACKS_SIMPLE = [
7408
7553
  var TRACKS_ADVANCED = [
7409
7554
  {
7410
7555
  id: "t0",
7411
- name: "TRACK 01",
7556
+ name: "\u30C8\u30E9\u30C3\u30AF1",
7412
7557
  color: [41, 173, 255],
7413
7558
  instrument: 0,
7414
7559
  volume: 100
7415
7560
  },
7416
7561
  {
7417
7562
  id: "t1",
7418
- name: "TRACK 02",
7563
+ name: "\u30C8\u30E9\u30C3\u30AF2",
7419
7564
  color: [0, 228, 54],
7420
7565
  instrument: 1,
7421
7566
  volume: 100
7422
7567
  },
7423
7568
  {
7424
7569
  id: "t2",
7425
- name: "TRACK 03",
7570
+ name: "\u30C8\u30E9\u30C3\u30AF3",
7426
7571
  color: [255, 119, 168],
7427
7572
  instrument: 2,
7428
7573
  volume: 100
7429
7574
  },
7430
7575
  {
7431
7576
  id: "t3",
7432
- name: "TRACK 04",
7577
+ name: "\u30C8\u30E9\u30C3\u30AF4",
7433
7578
  color: [255, 163, 0],
7434
7579
  instrument: 3,
7435
7580
  volume: 100
7436
7581
  },
7437
7582
  {
7438
7583
  id: "t4",
7439
- name: "TRACK 05",
7584
+ name: "\u30C8\u30E9\u30C3\u30AF5",
7440
7585
  color: [255, 236, 39],
7441
7586
  instrument: 4,
7442
7587
  volume: 100
7443
7588
  },
7444
7589
  {
7445
7590
  id: "t5",
7446
- name: "TRACK 06",
7591
+ name: "\u30C8\u30E9\u30C3\u30AF6",
7447
7592
  color: [131, 118, 156],
7448
7593
  instrument: 5,
7449
7594
  volume: 100
7450
7595
  },
7451
7596
  {
7452
7597
  id: "t6",
7453
- name: "TRACK 07",
7598
+ name: "\u30C8\u30E9\u30C3\u30AF7",
7454
7599
  color: [255, 0, 77],
7455
7600
  instrument: 6,
7456
7601
  volume: 100
7457
7602
  },
7458
7603
  {
7459
7604
  id: "t7",
7460
- name: "TRACK 08",
7605
+ name: "\u30C8\u30E9\u30C3\u30AF8",
7461
7606
  color: [255, 204, 170],
7462
7607
  instrument: 7,
7463
7608
  volume: 100
7464
7609
  },
7465
7610
  {
7466
7611
  id: "t8",
7467
- name: "TRACK 09",
7612
+ name: "\u30C8\u30E9\u30C3\u30AF9",
7468
7613
  color: [194, 195, 199],
7469
7614
  instrument: 8,
7470
7615
  volume: 100
7471
7616
  },
7472
7617
  {
7473
7618
  id: "t9",
7474
- name: "TRACK 10",
7619
+ name: "\u30C8\u30E9\u30C3\u30AF10",
7475
7620
  color: [0, 135, 81],
7476
7621
  instrument: 9,
7477
7622
  volume: 100
7478
7623
  },
7479
7624
  {
7480
7625
  id: "t10",
7481
- name: "TRACK 11",
7626
+ name: "\u30C8\u30E9\u30C3\u30AF11",
7482
7627
  color: [171, 82, 54],
7483
7628
  instrument: 10,
7484
7629
  volume: 100
7485
7630
  },
7486
7631
  {
7487
7632
  id: "t11",
7488
- name: "TRACK 12",
7633
+ name: "\u30C8\u30E9\u30C3\u30AF12",
7489
7634
  color: [126, 37, 83],
7490
7635
  instrument: 11,
7491
7636
  volume: 100
7492
7637
  },
7493
7638
  {
7494
7639
  id: "t12",
7495
- name: "TRACK 13",
7640
+ name: "\u30C8\u30E9\u30C3\u30AF13",
7496
7641
  color: [255, 241, 232],
7497
7642
  instrument: 12,
7498
7643
  volume: 100
7499
7644
  },
7500
7645
  {
7501
7646
  id: "t13",
7502
- name: "TRACK 14",
7647
+ name: "\u30C8\u30E9\u30C3\u30AF14",
7503
7648
  color: [120, 200, 255],
7504
7649
  instrument: 13,
7505
7650
  volume: 100
7506
7651
  },
7507
7652
  {
7508
7653
  id: "t14",
7509
- name: "TRACK 15",
7654
+ name: "\u30C8\u30E9\u30C3\u30AF15",
7510
7655
  color: [100, 255, 160],
7511
7656
  instrument: 14,
7512
7657
  volume: 100
@@ -7520,6 +7665,13 @@ var LYRIC_MODEL_LABELS2 = {
7520
7665
  };
7521
7666
  var lyricModelLabel = (model) => LYRIC_MODEL_LABELS2[model] ?? model;
7522
7667
  var clamp3 = (v, min, max) => Math.min(Math.max(v, min), max);
7668
+ var normalizeInstrumentName = (name) => {
7669
+ if (!name) return "";
7670
+ const stripped = name.replace(/\s+/g, "").toLowerCase();
7671
+ return GM_INSTRUMENT_NAMES.find(
7672
+ (n) => n.replace(/\s+/g, "").toLowerCase() === stripped
7673
+ ) ?? name;
7674
+ };
7523
7675
  var mountDAW = (target, options = {}) => {
7524
7676
  injectStyles();
7525
7677
  const getAudioTime = options.getAudioTime ?? (() => performance.now() / 1e3);
@@ -7559,7 +7711,8 @@ var mountDAW = (target, options = {}) => {
7559
7711
  let snapGridSteps = 12;
7560
7712
  const gridLineSteps = 48;
7561
7713
  let currentOffsetX = 0;
7562
- let currentOffsetY = (104 - 1 - 60) * renderConfig.keyHeight - 215;
7714
+ const _initPitch = options.initialScrollPitch;
7715
+ let currentOffsetY = _initPitch !== void 0 ? (renderConfig.keyCount - 1 - _initPitch) * renderConfig.keyHeight - 215 : (104 - 1 - 60) * renderConfig.keyHeight - 215;
7563
7716
  let playStartStep = 0;
7564
7717
  let isSolo = false;
7565
7718
  let lyricTrackIndices = /* @__PURE__ */ new Set();
@@ -7572,6 +7725,24 @@ var mountDAW = (target, options = {}) => {
7572
7725
  let copiedNotes = [];
7573
7726
  let trackStates = [];
7574
7727
  let suppressPatch = false;
7728
+ let lyricsDebounceTimer = null;
7729
+ const fireLyricsChange = (t) => {
7730
+ if (!options.onLyricsChange) return;
7731
+ const trackId = t.config.id;
7732
+ const data = {
7733
+ lyrics: t.lyrics,
7734
+ model: t.lyricModel,
7735
+ vocalVolume: t.vocalVolume,
7736
+ vocalGate: t.vocalGate,
7737
+ vocalPan: t.vocalPan,
7738
+ vocalOctave: t.vocalOctave
7739
+ };
7740
+ if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
7741
+ lyricsDebounceTimer = setTimeout(() => {
7742
+ options.onLyricsChange(trackId, data);
7743
+ lyricsDebounceTimer = null;
7744
+ }, 300);
7745
+ };
7575
7746
  const hiddenTracks = /* @__PURE__ */ new Set();
7576
7747
  const audioMutedTracks = /* @__PURE__ */ new Set();
7577
7748
  const createTrackStates = () => {
@@ -7620,7 +7791,8 @@ var mountDAW = (target, options = {}) => {
7620
7791
  vocalVolume: DEFAULT_VOCAL_VOLUME,
7621
7792
  vocalGate: 100,
7622
7793
  vocalPan: 64,
7623
- vocalOctave: 0
7794
+ vocalOctave: 0,
7795
+ trackInstrument: ""
7624
7796
  };
7625
7797
  });
7626
7798
  };
@@ -8326,19 +8498,20 @@ var mountDAW = (target, options = {}) => {
8326
8498
  };
8327
8499
  const updateTrackPanel = () => {
8328
8500
  refs.trackTabs.innerHTML = "";
8329
- for (const t of trackStates) {
8501
+ for (const [i, t] of trackStates.entries()) {
8330
8502
  const [r, g, b] = t.config.color;
8331
8503
  const btn = document.createElement("button");
8332
8504
  btn.className = `dtm-pill ${t.config.id === activeTrackId ? "dtm-pill--active" : ""}`;
8333
8505
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
8334
- btn.innerHTML = `<span class="dtm-dot"></span><span>${t.config.name}</span>`;
8506
+ btn.title = t.config.name;
8507
+ btn.textContent = String(i + 1);
8335
8508
  btn.addEventListener("click", () => switchTrack(t.config.id));
8336
8509
  refs.trackTabs.appendChild(btn);
8337
8510
  }
8338
8511
  const active = getActive();
8339
8512
  refs.trackBody.innerHTML = `
8340
8513
  <div class="dtm-row">
8341
- <span class="dtm-label">velocity</span>
8514
+ <span class="dtm-label">\u30D9\u30ED\u30B7\u30C6\u30A3</span>
8342
8515
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
8343
8516
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
8344
8517
  </div>`;
@@ -8353,6 +8526,59 @@ var mountDAW = (target, options = {}) => {
8353
8526
  active.core.setVolume(active.volume);
8354
8527
  volLabel.textContent = String(active.volume);
8355
8528
  });
8529
+ const instRow = document.createElement("div");
8530
+ instRow.className = "dtm-row";
8531
+ instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
8532
+ const instSel = document.createElement("select");
8533
+ instSel.className = "dtm-select dtm-grow";
8534
+ const defaultOpt = document.createElement("option");
8535
+ defaultOpt.value = "";
8536
+ defaultOpt.textContent = "\u30C7\u30D5\u30A9\u30EB\u30C8\uFF08\u30D7\u30EA\u30BB\u30C3\u30C8\uFF09";
8537
+ instSel.appendChild(defaultOpt);
8538
+ const GM_GROUPS = [
8539
+ "\u30D4\u30A2\u30CE",
8540
+ "\u30AF\u30ED\u30DE\u30C6\u30A3\u30C3\u30AF\u30D1\u30FC\u30AB\u30C3\u30B7\u30E7\u30F3",
8541
+ "\u30AA\u30EB\u30AC\u30F3",
8542
+ "\u30AE\u30BF\u30FC",
8543
+ "\u30D9\u30FC\u30B9",
8544
+ "\u30B9\u30C8\u30EA\u30F3\u30B0\u30B9",
8545
+ "\u30A2\u30F3\u30B5\u30F3\u30D6\u30EB",
8546
+ "\u30D6\u30E9\u30B9",
8547
+ "\u30EA\u30FC\u30C9\uFF08\u6728\u7BA1\uFF09",
8548
+ "\u30D1\u30A4\u30D7",
8549
+ "\u30B7\u30F3\u30BB\u30EA\u30FC\u30C9",
8550
+ "\u30B7\u30F3\u30BB\u30D1\u30C3\u30C9",
8551
+ "\u30B7\u30F3\u30BB\u30A8\u30D5\u30A7\u30AF\u30C8",
8552
+ "\u30A8\u30B9\u30CB\u30C3\u30AF",
8553
+ "\u30D1\u30FC\u30AB\u30C3\u30B7\u30D6",
8554
+ "\u30B5\u30A6\u30F3\u30C9\u30A8\u30D5\u30A7\u30AF\u30C8"
8555
+ ];
8556
+ GM_GROUPS.forEach((groupName, gi) => {
8557
+ const grp = document.createElement("optgroup");
8558
+ grp.label = groupName;
8559
+ for (let j = 0; j < 8; j++) {
8560
+ const name = GM_INSTRUMENT_NAMES[gi * 8 + j];
8561
+ if (!name) break;
8562
+ const o = document.createElement("option");
8563
+ o.value = name;
8564
+ o.textContent = name;
8565
+ grp.appendChild(o);
8566
+ }
8567
+ instSel.appendChild(grp);
8568
+ });
8569
+ instSel.value = normalizeInstrumentName(active.trackInstrument);
8570
+ const syncInstDisabled = () => {
8571
+ instSel.disabled = !!active.lyricModel;
8572
+ 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" : "";
8573
+ };
8574
+ syncInstDisabled();
8575
+ instSel.addEventListener("change", () => {
8576
+ active.trackInstrument = instSel.value;
8577
+ const trackIndex = trackStates.indexOf(active);
8578
+ options.onTrackInstrumentChange?.(trackIndex, active.trackInstrument);
8579
+ });
8580
+ instRow.appendChild(instSel);
8581
+ refs.trackBody.appendChild(instRow);
8356
8582
  if (isAdvanced || active.config.id !== "chord") {
8357
8583
  const lyricDiv = document.createElement("div");
8358
8584
  lyricDiv.className = "dtm-row";
@@ -8360,7 +8586,7 @@ var mountDAW = (target, options = {}) => {
8360
8586
  lyricDiv.style.alignItems = "stretch";
8361
8587
  lyricDiv.innerHTML = `
8362
8588
  <div class="dtm-row">
8363
- <span class="dtm-label">\u266A \u6B4C\u8A5E</span>
8589
+ <span class="dtm-label">\u266A UTAU</span>
8364
8590
  <select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
8365
8591
  <img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
8366
8592
  <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">
@@ -8434,7 +8660,7 @@ var mountDAW = (target, options = {}) => {
8434
8660
  o.textContent = label;
8435
8661
  lyricModelSel.appendChild(o);
8436
8662
  };
8437
- addOpt("", "\u306A\u3057");
8663
+ addOpt("", "\u30DC\u30FC\u30AB\u30EB\u306A\u3057");
8438
8664
  for (const m of LYRIC_MODELS) addOpt(m, lyricModelLabel(m));
8439
8665
  if (active.lyricModel && !LYRIC_MODELS.includes(active.lyricModel)) {
8440
8666
  addOpt(active.lyricModel, lyricModelLabel(active.lyricModel));
@@ -8483,21 +8709,27 @@ var mountDAW = (target, options = {}) => {
8483
8709
  lyricModelSel.addEventListener("change", () => {
8484
8710
  active.lyricModel = lyricModelSel.value;
8485
8711
  syncLyricVisibility();
8712
+ syncInstDisabled();
8713
+ fireLyricsChange(active);
8486
8714
  });
8487
8715
  lyricOctaveSel.addEventListener("change", () => {
8488
8716
  active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
8717
+ fireLyricsChange(active);
8489
8718
  });
8490
8719
  lyricInput.addEventListener("input", () => {
8491
8720
  active.lyrics = lyricInput.value;
8492
8721
  updateLyricCount();
8722
+ fireLyricsChange(active);
8493
8723
  });
8494
8724
  lyricVol.addEventListener("input", () => {
8495
8725
  active.vocalVolume = Number.parseInt(lyricVol.value, 10);
8496
8726
  lyricVolLabel.textContent = lyricVol.value;
8727
+ fireLyricsChange(active);
8497
8728
  });
8498
8729
  lyricPan.addEventListener("input", () => {
8499
8730
  active.vocalPan = Number.parseInt(lyricPan.value, 10);
8500
8731
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
8732
+ fireLyricsChange(active);
8501
8733
  });
8502
8734
  lyricPanLabel.style.cursor = "pointer";
8503
8735
  lyricPanLabel.title = "\u30BF\u30C3\u30D7\u3067\u4E2D\u592E(C)\u3078";
@@ -8505,6 +8737,7 @@ var mountDAW = (target, options = {}) => {
8505
8737
  active.vocalPan = 64;
8506
8738
  lyricPan.value = "64";
8507
8739
  lyricPanLabel.textContent = fmtPan(64);
8740
+ fireLyricsChange(active);
8508
8741
  });
8509
8742
  }
8510
8743
  if (active.config.id === "chord" && showChord) {
@@ -8579,13 +8812,19 @@ var mountDAW = (target, options = {}) => {
8579
8812
  const barLimitBars = Number(refs.barLimitSelect.value);
8580
8813
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
8581
8814
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
8815
+ const trackInstrumentsForMeta = {};
8816
+ trackStates.forEach((t, i) => {
8817
+ if (t.trackInstrument) trackInstrumentsForMeta[i] = t.trackInstrument;
8818
+ });
8819
+ const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
8582
8820
  const metaLineFull = formatMmlMeta(
8583
8821
  {
8584
8822
  instrument: currentInstrument || void 0,
8585
8823
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8586
8824
  volume: masterVolume,
8587
8825
  drumVolume,
8588
- mode
8826
+ mode,
8827
+ trackInstruments: trackInstMeta
8589
8828
  },
8590
8829
  " "
8591
8830
  );
@@ -8595,7 +8834,8 @@ var mountDAW = (target, options = {}) => {
8595
8834
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8596
8835
  volume: masterVolume,
8597
8836
  drumVolume,
8598
- mode
8837
+ mode,
8838
+ trackInstruments: trackInstMeta
8599
8839
  },
8600
8840
  ""
8601
8841
  );
@@ -8753,6 +8993,13 @@ var mountDAW = (target, options = {}) => {
8753
8993
  refs.drumVolume.value = String(meta.drumVolume);
8754
8994
  refs.drumVolumeLabel.textContent = `${meta.drumVolume}%`;
8755
8995
  }
8996
+ trackStates.forEach((t, i) => {
8997
+ const name = normalizeInstrumentName(meta.trackInstruments?.[i] ?? "");
8998
+ if (t.trackInstrument !== name) {
8999
+ t.trackInstrument = name;
9000
+ options.onTrackInstrumentChange?.(i, name);
9001
+ }
9002
+ });
8756
9003
  for (const t of trackStates) {
8757
9004
  t.lyrics = "";
8758
9005
  t.lyricModel = "";
@@ -8912,6 +9159,31 @@ var mountDAW = (target, options = {}) => {
8912
9159
  setLoading(false);
8913
9160
  }, 30);
8914
9161
  };
9162
+ const showConfirmModal = (message) => new Promise((resolve) => {
9163
+ const overlay = document.createElement("div");
9164
+ overlay.className = "dtm-modal-overlay";
9165
+ overlay.innerHTML = `
9166
+ <div class="dtm-modal">
9167
+ <div class="dtm-modal-header">
9168
+ <span class="dtm-modal-title">\u30E2\u30FC\u30C9\u306E\u78BA\u8A8D</span>
9169
+ </div>
9170
+ <div class="dtm-modal-body"><p>${message}</p></div>
9171
+ <div class="dtm-confirm-footer">
9172
+ <button class="dtm-btn dtm-btn--ghost dtm-confirm-no">\u3044\u3044\u3048\uFF08\u3053\u306E\u307E\u307E\u8AAD\u307F\u8FBC\u3080\uFF09</button>
9173
+ <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>
9174
+ </div>
9175
+ </div>`;
9176
+ const close = (result) => {
9177
+ overlay.remove();
9178
+ resolve(result);
9179
+ };
9180
+ overlay.querySelector(".dtm-confirm-yes").addEventListener("click", () => close(true));
9181
+ overlay.querySelector(".dtm-confirm-no").addEventListener("click", () => close(false));
9182
+ overlay.addEventListener("click", (e) => {
9183
+ if (e.target === overlay) close(false);
9184
+ });
9185
+ document.body.appendChild(overlay);
9186
+ });
8915
9187
  const wireEvents = () => {
8916
9188
  refs.playBtn.addEventListener("click", togglePlay);
8917
9189
  refs.playBtn.disabled = false;
@@ -9031,10 +9303,25 @@ var mountDAW = (target, options = {}) => {
9031
9303
  "click",
9032
9304
  () => copy(refs.outputMini.textContent ?? "", refs.copyMiniBtn)
9033
9305
  );
9034
- refs.mmlLoadBtn.addEventListener(
9035
- "click",
9036
- () => overlayDuring(() => loadMML(refs.mmlInput.value))
9037
- );
9306
+ refs.mmlLoadBtn.addEventListener("click", async () => {
9307
+ const mml = refs.mmlInput.value;
9308
+ if (!isAdvanced && options.onRequestAdvancedMode) {
9309
+ const { mergedTrackCount } = parseMML(mml, {
9310
+ stepsPerBar: renderConfig.stepsPerBar,
9311
+ clampTrackCount: trackStates.length
9312
+ });
9313
+ if (mergedTrackCount > 0) {
9314
+ const confirmed = await showConfirmModal(
9315
+ "\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"
9316
+ );
9317
+ if (confirmed) {
9318
+ options.onRequestAdvancedMode(mml);
9319
+ return;
9320
+ }
9321
+ }
9322
+ }
9323
+ overlayDuring(() => loadMML(mml));
9324
+ });
9038
9325
  let activeSamplePlayer = null;
9039
9326
  let activeSampleButton = null;
9040
9327
  const collapseActiveSample = () => {
@@ -9127,6 +9414,7 @@ var mountDAW = (target, options = {}) => {
9127
9414
  singingVoices: options.singingVoices,
9128
9415
  drumPatterns: options.drumPatterns,
9129
9416
  volume: masterVolume,
9417
+ _skipInfoModals: true,
9130
9418
  onStop: () => {
9131
9419
  if (activeSampleButton === htmlBtn) {
9132
9420
  htmlBtn.textContent = "\u25B6 \u8A66\u8074";
@@ -9208,7 +9496,7 @@ var mountDAW = (target, options = {}) => {
9208
9496
  refs.overlay.hidden = true;
9209
9497
  setLoading(false);
9210
9498
  });
9211
- refs.midiLoadBtn.addEventListener("click", () => {
9499
+ refs.midiLoadBtn.addEventListener("click", async () => {
9212
9500
  if (!pendingMidi) return;
9213
9501
  const selected = [];
9214
9502
  const btns = refs.midiTrackSelection.querySelectorAll("button");
@@ -9217,6 +9505,19 @@ var mountDAW = (target, options = {}) => {
9217
9505
  selected.push(detectedTracks[i].index);
9218
9506
  });
9219
9507
  if (selected.length === 0) return;
9508
+ if (!isAdvanced && options.onRequestAdvancedMode && selected.length > trackStates.length) {
9509
+ const confirmed = await showConfirmModal(
9510
+ "\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"
9511
+ );
9512
+ if (confirmed) {
9513
+ const midi = pendingMidi;
9514
+ const sel = selected.slice();
9515
+ options.onRequestAdvancedMode(void 0, (newDaw) => {
9516
+ newDaw.applyMidiParsed?.(midi, sel);
9517
+ });
9518
+ return;
9519
+ }
9520
+ }
9220
9521
  overlayDuring(() => applyMidiSelection(pendingMidi, selected));
9221
9522
  });
9222
9523
  };
@@ -9353,6 +9654,9 @@ var mountDAW = (target, options = {}) => {
9353
9654
  },
9354
9655
  loadMML,
9355
9656
  loadMIDI,
9657
+ applyMidiParsed: (midi, selectedIndices) => {
9658
+ overlayDuring(() => applyMidiSelection(midi, selectedIndices));
9659
+ },
9356
9660
  exportMIDI: exportMIDI2,
9357
9661
  setBpm,
9358
9662
  getPlaybackState: () => playbackState,
@@ -9387,6 +9691,37 @@ var mountDAW = (target, options = {}) => {
9387
9691
  if (audible) audioMutedTracks.delete(trackId);
9388
9692
  else audioMutedTracks.add(trackId);
9389
9693
  },
9694
+ applyLyrics: (trackId, data) => {
9695
+ const t = trackStates.find((s) => s.config.id === trackId);
9696
+ if (!t) return;
9697
+ t.lyrics = data.lyrics;
9698
+ t.lyricModel = data.model;
9699
+ t.vocalVolume = data.vocalVolume;
9700
+ t.vocalGate = data.vocalGate;
9701
+ t.vocalPan = data.vocalPan;
9702
+ t.vocalOctave = data.vocalOctave;
9703
+ },
9704
+ applyTrackInstrument: (trackIndex, instrumentName) => {
9705
+ const t = trackStates[trackIndex];
9706
+ if (!t) return;
9707
+ const name = normalizeInstrumentName(instrumentName);
9708
+ t.trackInstrument = name;
9709
+ if (t.config.id === activeTrackId) updateTrackPanel();
9710
+ },
9711
+ noteToCanvas: (step, pitch) => {
9712
+ const canvas = getGridCanvas();
9713
+ const x = step * renderConfig.stepWidth - currentOffsetX;
9714
+ const y = (renderConfig.keyCount - 1 - pitch) * renderConfig.keyHeight - currentOffsetY;
9715
+ const onScreen = x >= 0 && x <= canvas.width && y >= 0 && y <= canvas.height;
9716
+ let side = null;
9717
+ if (!onScreen) {
9718
+ if (x < 0) side = "left";
9719
+ else if (x > canvas.width) side = "right";
9720
+ else if (y < 0) side = "top";
9721
+ else side = "bottom";
9722
+ }
9723
+ return { x, y, onScreen, side };
9724
+ },
9390
9725
  destroy: () => {
9391
9726
  sequencer.stop();
9392
9727
  options.singingVoices?.stopStream();
@@ -10037,7 +10372,7 @@ var SoundFont = class _SoundFont {
10037
10372
  const { buffer, _param } = zone;
10038
10373
  if (!buffer || !_param) return;
10039
10374
  src.buffer = buffer;
10040
- g.gain.value = volume;
10375
+ g.gain.setValueAtTime(volume, _when);
10041
10376
  src.playbackRate.setValueAtTime(_param.playbackRate, 0);
10042
10377
  Object.assign(src, _param.src);
10043
10378
  const _duration = duration + _SoundFont.afterTime;
@@ -10306,6 +10641,14 @@ var createDtmStudio = async (options = {}) => {
10306
10641
  }
10307
10642
  })();
10308
10643
  let nameToKey = {};
10644
+ const resolveNameToKey = (name) => {
10645
+ if (nameToKey[name]) return nameToKey[name];
10646
+ const stripped = name.replace(/\s+/g, "").toLowerCase();
10647
+ const canonical = Object.keys(nameToKey).find(
10648
+ (k) => k.replace(/\s+/g, "").toLowerCase() === stripped
10649
+ );
10650
+ return canonical ? nameToKey[canonical] : void 0;
10651
+ };
10309
10652
  const soundFonts = /* @__PURE__ */ new Map();
10310
10653
  const loadingByKey = /* @__PURE__ */ new Map();
10311
10654
  const loadInstrument = (instrumentKey) => {
@@ -10391,7 +10734,7 @@ var createDtmStudio = async (options = {}) => {
10391
10734
  if (opts.label !== null) {
10392
10735
  const lab = doc.createElement("span");
10393
10736
  lab.className = "dtm-controlbar-label";
10394
- lab.textContent = opts.label ?? "INSTRUMENT";
10737
+ lab.textContent = opts.label ?? "\u697D\u5668\u30D7\u30EA\u30BB\u30C3\u30C8";
10395
10738
  wrapper.appendChild(lab);
10396
10739
  }
10397
10740
  const select = doc.createElement("select");
@@ -10456,7 +10799,13 @@ var createDtmStudio = async (options = {}) => {
10456
10799
  const mountedPlayers = [];
10457
10800
  const mountedModeSwitches = [];
10458
10801
  const mountEditor = (target, opts = {}) => {
10459
- const { preset, presetUI, onInstrumentChange, ...dawOverrides } = opts;
10802
+ const {
10803
+ preset,
10804
+ presetUI,
10805
+ onInstrumentChange,
10806
+ onTrackInstrumentChange: externalOnTrackInstrumentChange,
10807
+ ...dawOverrides
10808
+ } = opts;
10460
10809
  const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
10461
10810
  const trackIds = tracks.map((t) => t.id);
10462
10811
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
@@ -10464,14 +10813,29 @@ var createDtmStudio = async (options = {}) => {
10464
10813
  const initialPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : presetKey;
10465
10814
  let editorPreset = initialPreset;
10466
10815
  const isAdvancedMode = dawOverrides.mode === "advanced";
10816
+ const trackInstOverrides = /* @__PURE__ */ new Map();
10817
+ if (meta.trackInstruments) {
10818
+ for (const [idxStr, name] of Object.entries(meta.trackInstruments)) {
10819
+ const idx = Number(idxStr);
10820
+ const key = resolveNameToKey(name);
10821
+ if (key) trackInstOverrides.set(idx, key);
10822
+ }
10823
+ }
10467
10824
  const playNote = (e) => {
10468
- const sf2 = resolveSoundFont(
10469
- editorPreset,
10470
- e.trackId,
10471
- isAdvancedMode ? "advanced" : "simple"
10472
- );
10473
- if (!sf2) return;
10474
- sf2.play({
10825
+ const trackIdx = tracks.findIndex((t) => t.id === e.trackId);
10826
+ const overrideKey = trackIdx >= 0 ? trackInstOverrides.get(trackIdx) : void 0;
10827
+ let sfInst;
10828
+ if (overrideKey) {
10829
+ sfInst = soundFonts.get(overrideKey);
10830
+ } else {
10831
+ sfInst = resolveSoundFont(
10832
+ editorPreset,
10833
+ e.trackId,
10834
+ isAdvancedMode ? "advanced" : "simple"
10835
+ );
10836
+ }
10837
+ if (!sfInst) return;
10838
+ sfInst.play({
10475
10839
  ctx: audioCtx,
10476
10840
  destination: masterGain,
10477
10841
  pitch: e.pitch,
@@ -10488,6 +10852,17 @@ var createDtmStudio = async (options = {}) => {
10488
10852
  }
10489
10853
  onInstrumentChange?.(key);
10490
10854
  };
10855
+ const handleTrackInstrumentChange = async (trackIndex, instrumentName) => {
10856
+ if (!instrumentName) {
10857
+ trackInstOverrides.delete(trackIndex);
10858
+ return;
10859
+ }
10860
+ await listReady;
10861
+ const key = resolveNameToKey(instrumentName);
10862
+ if (!key) return;
10863
+ trackInstOverrides.set(trackIndex, key);
10864
+ await loadInstrument(key);
10865
+ };
10491
10866
  const base = {
10492
10867
  getAudioTime: () => audioCtx.currentTime,
10493
10868
  onResumeAudio: resumeAudio,
@@ -10496,6 +10871,10 @@ var createDtmStudio = async (options = {}) => {
10496
10871
  singingVoices,
10497
10872
  parseMidi,
10498
10873
  onInstrumentChange: handleInstrumentChange,
10874
+ onTrackInstrumentChange: (idx, name) => {
10875
+ void handleTrackInstrumentChange(idx, name);
10876
+ externalOnTrackInstrumentChange?.(idx, name);
10877
+ },
10499
10878
  ...dawOverrides
10500
10879
  };
10501
10880
  const daw = mountDAW(target, base);
@@ -10543,6 +10922,10 @@ var createDtmStudio = async (options = {}) => {
10543
10922
  presetSelect.setValue(name);
10544
10923
  }
10545
10924
  },
10925
+ applyTrackInstrument: (trackIndex, instrumentName) => {
10926
+ daw.applyTrackInstrument(trackIndex, instrumentName);
10927
+ void handleTrackInstrumentChange(trackIndex, instrumentName);
10928
+ },
10546
10929
  destroy
10547
10930
  };
10548
10931
  };
@@ -10550,8 +10933,8 @@ var createDtmStudio = async (options = {}) => {
10550
10933
  const doc = target.ownerDocument;
10551
10934
  const tracksFor = opts.tracksFor ?? ((m) => m === "advanced" ? TRACKS_ADVANCED : TRACKS_SIMPLE);
10552
10935
  const labels = {
10553
- simple: opts.labels?.simple ?? "\u30B7\u30F3\u30D7\u30EB",
10554
- advanced: opts.labels?.advanced ?? "\u30A2\u30C9\u30D0\u30F3\u30B9"
10936
+ simple: opts.labels?.simple ?? "\u521D\u5FC3\u8005",
10937
+ advanced: opts.labels?.advanced ?? "\u4E0A\u7D1A\u8005"
10555
10938
  };
10556
10939
  const editorOptionsFor = (mode) => typeof opts.editorOptions === "function" ? opts.editorOptions(mode) : opts.editorOptions ?? {};
10557
10940
  let currentMode = opts.mode ?? "simple";
@@ -10561,7 +10944,7 @@ var createDtmStudio = async (options = {}) => {
10561
10944
  if (opts.label !== null) {
10562
10945
  const lab = doc.createElement("span");
10563
10946
  lab.className = "dtm-controlbar-label";
10564
- lab.textContent = opts.label ?? "MODE";
10947
+ lab.textContent = opts.label ?? "\u30E2\u30FC\u30C9";
10565
10948
  wrapper.appendChild(lab);
10566
10949
  }
10567
10950
  const seg = doc.createElement("div");
@@ -10592,7 +10975,15 @@ var createDtmStudio = async (options = {}) => {
10592
10975
  ...editorOpts,
10593
10976
  mode,
10594
10977
  tracks: tracksFor(mode),
10595
- initialMML: mml ?? editorOpts.initialMML
10978
+ initialMML: mml ?? editorOpts.initialMML,
10979
+ onRequestAdvancedMode: (pendingMml, applyMidi) => {
10980
+ doUnmount();
10981
+ currentMode = "advanced";
10982
+ updateButtons();
10983
+ opts.onChange?.("advanced");
10984
+ doMount("advanced", pendingMml);
10985
+ if (applyMidi && daw) applyMidi(daw);
10986
+ }
10596
10987
  });
10597
10988
  attachWrapper();
10598
10989
  opts.onMount?.(daw, mode);
@@ -10642,24 +11033,44 @@ var createDtmStudio = async (options = {}) => {
10642
11033
  (idx) => getRoleForTrackIndex(idx, isAdvancedMode ? "advanced" : "simple")
10643
11034
  );
10644
11035
  const loadTrackIds = trackIds.length > 0 ? trackIds : [...TRACK_ROLES];
10645
- void loadPreset(
10646
- playerPreset,
10647
- loadTrackIds,
10648
- isAdvancedMode ? "advanced" : "simple"
10649
- );
10650
- const playPlayerNote = (e) => {
10651
- const idx = Number(e.trackId);
10652
- const role = getRoleForTrackIndex(
10653
- idx,
10654
- isAdvancedMode ? "advanced" : "simple"
10655
- );
10656
- const sf2 = resolveSoundFont(
11036
+ const playerTrackInstKeys = /* @__PURE__ */ new Map();
11037
+ const loadPlayerTrackInstruments = async () => {
11038
+ if (!meta.trackInstruments) return;
11039
+ await listReady;
11040
+ for (const [idxStr, name] of Object.entries(meta.trackInstruments)) {
11041
+ const key = resolveNameToKey(name);
11042
+ if (!key) continue;
11043
+ playerTrackInstKeys.set(Number(idxStr), key);
11044
+ await loadInstrument(key);
11045
+ }
11046
+ };
11047
+ void Promise.all([
11048
+ loadPreset(
10657
11049
  playerPreset,
10658
- role,
11050
+ loadTrackIds,
10659
11051
  isAdvancedMode ? "advanced" : "simple"
10660
- );
10661
- if (!sf2) return;
10662
- sf2.play({
11052
+ ),
11053
+ loadPlayerTrackInstruments()
11054
+ ]);
11055
+ const playPlayerNote = (e) => {
11056
+ const idx = Number(e.trackId);
11057
+ const overrideKey = playerTrackInstKeys.get(idx);
11058
+ let sfInst;
11059
+ if (overrideKey) {
11060
+ sfInst = soundFonts.get(overrideKey);
11061
+ } else {
11062
+ const role = getRoleForTrackIndex(
11063
+ idx,
11064
+ isAdvancedMode ? "advanced" : "simple"
11065
+ );
11066
+ sfInst = resolveSoundFont(
11067
+ playerPreset,
11068
+ role,
11069
+ isAdvancedMode ? "advanced" : "simple"
11070
+ );
11071
+ }
11072
+ if (!sfInst) return;
11073
+ sfInst.play({
10663
11074
  ctx: audioCtx,
10664
11075
  destination: masterGain,
10665
11076
  pitch: e.pitch,
@@ -10718,6 +11129,7 @@ var createDtmStudio = async (options = {}) => {
10718
11129
  DRUM_FONT,
10719
11130
  DRUM_KEYS,
10720
11131
  DRUM_PATTERNS,
11132
+ GM_INSTRUMENT_NAMES,
10721
11133
  INSTRUMENT_PRESETS,
10722
11134
  KOE_BASE_URL,
10723
11135
  KOE_VOICEBANKS,
@@ -10761,7 +11173,6 @@ var createDtmStudio = async (options = {}) => {
10761
11173
  exportMIDI,
10762
11174
  extractMidiPlacements,
10763
11175
  extractMidiPlacementsByTrack,
10764
- fetchSoundFontList,
10765
11176
  formatMmlMeta,
10766
11177
  freqFromPitch,
10767
11178
  generateRandomPattern,