@onjmin/dtm 0.1.22 → 0.1.24

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
@@ -1216,6 +1216,9 @@ var ICONS = {
1216
1216
  },
1217
1217
  info: {
1218
1218
  d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
1219
+ },
1220
+ more: {
1221
+ d: "M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
1219
1222
  }
1220
1223
  };
1221
1224
  var icon = (name, size = 20) => {
@@ -1342,6 +1345,7 @@ var buildUI = (target, options) => {
1342
1345
  <textarea class="dtm-textarea dtm-grow" data-dtm="mml-input" placeholder="MML\u3092\u5165\u529B"></textarea>
1343
1346
  <button class="dtm-btn dtm-btn--primary" data-dtm="mml-load">\u8AAD\u8FBC</button>
1344
1347
  </div>
1348
+ <p class="dtm-load-note dtm-hidden" data-dtm="mml-load-note"></p>
1345
1349
  </div>
1346
1350
  </details>
1347
1351
 
@@ -1467,6 +1471,7 @@ var buildUI = (target, options) => {
1467
1471
  midiPanel: sel("midi-panel"),
1468
1472
  mmlInput: sel("mml-input"),
1469
1473
  mmlLoadBtn: sel("mml-load"),
1474
+ mmlLoadNote: sel("mml-load-note"),
1470
1475
  shiftSelect: sel("shift-select"),
1471
1476
  shiftApplyBtn: sel("shift-apply"),
1472
1477
  macroClear: sel("macro-clear"),
@@ -1620,6 +1625,110 @@ var DRUM_PATTERNS = {
1620
1625
  ]
1621
1626
  };
1622
1627
 
1628
+ // src/instrument-presets.ts
1629
+ var INSTRUMENT_PRESETS = {
1630
+ // --- STANDARD: 汎用性と完成度重視 ---
1631
+ piano: {
1632
+ displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
1633
+ description: "\u6700\u3082\u7834\u7DBB\u3057\u306B\u304F\u3044\u69CB\u6210\u3002\u697D\u66F2\u5236\u4F5C\u306E\u30B9\u30B1\u30C3\u30C1\u306B\u3082\u6700\u9069\u3002",
1634
+ melody: "Acoustic Grand Piano",
1635
+ submelody: "Vibraphone",
1636
+ bass: "Electric Bass (finger)",
1637
+ chord: "Pad 2 (warm)"
1638
+ },
1639
+ acoustic: {
1640
+ displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
1641
+ description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
1642
+ melody: "Acoustic Guitar (steel)",
1643
+ submelody: "Harmonica",
1644
+ bass: "Acoustic Bass",
1645
+ chord: "Acoustic Guitar (nylon)"
1646
+ },
1647
+ jazz_night: {
1648
+ displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
1649
+ description: "Rhodes\u98A8\u306EEP\u3068\u30A6\u30C3\u30C9\u30D9\u30FC\u30B9\u306B\u3088\u308B\u3001\u5927\u4EBA\u3073\u305F\u30A2\u30F3\u30B5\u30F3\u30D6\u30EB\u3002",
1650
+ melody: "Electric Piano 1",
1651
+ submelody: "Flute",
1652
+ bass: "Acoustic Bass",
1653
+ chord: "Electric Guitar (jazz)"
1654
+ },
1655
+ // --- MODERN & VIBE: エッジの効いた現代的な響き ---
1656
+ synth_pop: {
1657
+ displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
1658
+ description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
1659
+ melody: "Lead 2 (sawtooth)",
1660
+ submelody: "Lead 4 (chiff)",
1661
+ bass: "Synth Bass 2",
1662
+ chord: "Pad 3 (polysynth)"
1663
+ },
1664
+ cyber_punk: {
1665
+ displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
1666
+ description: "\u30C7\u30B8\u30BF\u30EB\u306A\u51B7\u305F\u3055\u3068\u6B6A\u307F\u304C\u6DF7\u3056\u308A\u5408\u3046\u3001\u672A\u6765\u7684\u306A\u97FF\u304D\u3002",
1667
+ melody: "Lead 8 (bass + lead)",
1668
+ submelody: "Lead 5 (charang)",
1669
+ bass: "Synth Bass 2",
1670
+ chord: "Pad 8 (sweep)"
1671
+ },
1672
+ rock: {
1673
+ displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
1674
+ description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
1675
+ melody: "Distortion Guitar",
1676
+ submelody: "Rock Organ",
1677
+ bass: "Electric Bass (pick)",
1678
+ chord: "Overdriven Guitar"
1679
+ },
1680
+ // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
1681
+ orchestra: {
1682
+ displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
1683
+ description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
1684
+ melody: "French Horn",
1685
+ submelody: "Pizzicato Strings",
1686
+ bass: "Cello",
1687
+ chord: "Tremolo Strings"
1688
+ },
1689
+ japanese_wa: {
1690
+ displayName: "\u548C\u98A8\u30FB\u96C5",
1691
+ description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
1692
+ melody: "Koto",
1693
+ submelody: "Shamisen",
1694
+ bass: "Taiko Drum",
1695
+ chord: "Shakuhachi"
1696
+ },
1697
+ arabic_exotic: {
1698
+ displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
1699
+ description: "\u30B7\u30BF\u30FC\u30EB\u3084\u30D0\u30B0\u30D1\u30A4\u30D7\u306B\u3088\u308B\u3001\u7570\u56FD\u60C5\u7DD2\u6EA2\u308C\u308B\u30B5\u30A6\u30F3\u30C9\u3002",
1700
+ melody: "Sitar",
1701
+ submelody: "Bagpipe",
1702
+ bass: "Fretless Bass",
1703
+ chord: "Kalimba"
1704
+ },
1705
+ // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
1706
+ fantasy_rpg: {
1707
+ displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
1708
+ description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
1709
+ melody: "Ocarina",
1710
+ submelody: "Celesta",
1711
+ bass: "Timpani",
1712
+ chord: "Orchestral Harp"
1713
+ },
1714
+ ambient_cloud: {
1715
+ displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
1716
+ description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
1717
+ melody: "Lead 6 (voice)",
1718
+ submelody: "Music Box",
1719
+ bass: "Synth Bass 1",
1720
+ chord: "Pad 7 (halo)"
1721
+ },
1722
+ retro_game: {
1723
+ displayName: "8-bit \u30EC\u30C8\u30ED",
1724
+ description: "\u77E9\u5F62\u6CE2\u3092\u60F3\u8D77\u3055\u305B\u308B\u3001\u521D\u671F\u30B2\u30FC\u30E0\u6A5F\u306E\u3088\u3046\u306A\u61D0\u304B\u3057\u3044\u97FF\u304D\u3002",
1725
+ melody: "Lead 1 (square)",
1726
+ submelody: "Lead 2 (sawtooth)",
1727
+ bass: "Synth Bass 1",
1728
+ chord: "Clavinet"
1729
+ }
1730
+ };
1731
+
1623
1732
  // node_modules/.pnpm/@onjmin+koe@1.0.3/node_modules/@onjmin/koe/dist/index.js
1624
1733
  var MAGIC = 1263486208;
1625
1734
  function parseKoeHeader(headerBytes) {
@@ -4028,7 +4137,7 @@ var PITCH_MAP2 = {
4028
4137
  b: 11
4029
4138
  };
4030
4139
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
4031
- var META_DIRECTIVE = /#(inst|drum|volume)=([\w-]+)/gi;
4140
+ var META_DIRECTIVE = /#(inst|drum|volume|mode)=([\w-]+)/gi;
4032
4141
  var parseMmlMeta = (mml) => {
4033
4142
  const meta = {};
4034
4143
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -4038,6 +4147,10 @@ var parseMmlMeta = (mml) => {
4038
4147
  else if (key === "volume") {
4039
4148
  const v = Number.parseInt(m[2], 10);
4040
4149
  if (!Number.isNaN(v)) meta.volume = v;
4150
+ } else if (key === "mode") {
4151
+ if (m[2] === "simple" || m[2] === "advanced") {
4152
+ meta.mode = m[2];
4153
+ }
4041
4154
  }
4042
4155
  }
4043
4156
  return meta;
@@ -4048,6 +4161,7 @@ var formatMmlMeta = (meta) => {
4048
4161
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
4049
4162
  if (meta.drum) parts.push(`#drum=${meta.drum}`);
4050
4163
  if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
4164
+ if (meta.mode) parts.push(`#mode=${meta.mode}`);
4051
4165
  return parts.join(" ");
4052
4166
  };
4053
4167
  var parseMML = (mml, options = {}) => {
@@ -4064,6 +4178,7 @@ var parseMML = (mml, options = {}) => {
4064
4178
  bpm,
4065
4179
  tokenTracks: collectTokens ? tokenTracks : void 0,
4066
4180
  lyrics: collectLyrics ? /* @__PURE__ */ new Map() : void 0,
4181
+ mergedTrackCount: 0,
4067
4182
  meta: {}
4068
4183
  };
4069
4184
  }
@@ -4076,14 +4191,26 @@ var parseMML = (mml, options = {}) => {
4076
4191
  const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
4077
4192
  const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
4078
4193
  let trackIndex = 0;
4194
+ let sourceTrackIndex = 0;
4079
4195
  let octave = 4;
4080
4196
  let currentStep = 0;
4081
4197
  let baseLength = 16;
4198
+ const contributors = /* @__PURE__ */ new Map();
4199
+ const recordContributor = () => {
4200
+ let set = contributors.get(trackIndex);
4201
+ if (!set) {
4202
+ set = /* @__PURE__ */ new Set();
4203
+ contributors.set(trackIndex, set);
4204
+ }
4205
+ set.add(sourceTrackIndex);
4206
+ };
4082
4207
  for (const rawPart of parts) {
4083
4208
  const part = rawPart.trim();
4084
4209
  if (part.startsWith("@")) {
4085
4210
  let idx = Number.parseInt(part.substring(1), 10);
4086
- if (clampTrackCount !== void 0 && idx >= clampTrackCount) idx = 2;
4211
+ sourceTrackIndex = idx;
4212
+ if (clampTrackCount !== void 0 && idx >= clampTrackCount)
4213
+ idx = clampTrackCount - 1;
4087
4214
  trackIndex = idx;
4088
4215
  octave = 4;
4089
4216
  currentStep = 0;
@@ -4205,6 +4332,7 @@ var parseMML = (mml, options = {}) => {
4205
4332
  }
4206
4333
  if (j < body.length && body[j] === "]") j++;
4207
4334
  const steps = parseLength();
4335
+ if (chordNotes.length > 0) recordContributor();
4208
4336
  for (const p of chordNotes) {
4209
4337
  placements.push({
4210
4338
  trackIndex,
@@ -4228,6 +4356,7 @@ var parseMML = (mml, options = {}) => {
4228
4356
  }
4229
4357
  const midiPitch = (octave + 1) * 12 + pitch;
4230
4358
  const steps = parseLength();
4359
+ recordContributor();
4231
4360
  placements.push({
4232
4361
  trackIndex,
4233
4362
  startStep: currentStep,
@@ -4241,11 +4370,16 @@ var parseMML = (mml, options = {}) => {
4241
4370
  }
4242
4371
  }
4243
4372
  }
4373
+ let mergedTrackCount = 0;
4374
+ for (const set of contributors.values()) {
4375
+ if (set.size >= 2) mergedTrackCount++;
4376
+ }
4244
4377
  return {
4245
4378
  placements,
4246
4379
  bpm,
4247
4380
  tokenTracks: collectTokens ? tokenTracks : void 0,
4248
4381
  lyrics,
4382
+ mergedTrackCount,
4249
4383
  meta
4250
4384
  };
4251
4385
  };
@@ -4307,6 +4441,8 @@ var createSequencer = (options) => {
4307
4441
  let active = false;
4308
4442
  let fromStepValue = 0;
4309
4443
  let trackVolumeMap = /* @__PURE__ */ new Map();
4444
+ let lastRealTime = -1;
4445
+ let lastAudioTime = -1;
4310
4446
  let isLooping = false;
4311
4447
  let loopStartStep = 0;
4312
4448
  let loopEndStep = 0;
@@ -4380,6 +4516,21 @@ var createSequencer = (options) => {
4380
4516
  const sps = secondsPerStep();
4381
4517
  const time = options.getAudioTime() - startTime;
4382
4518
  const soloId = options.getSoloTrackId();
4519
+ const nowReal = performance.now() / 1e3;
4520
+ if (lastRealTime > 0 && lastAudioTime >= 0) {
4521
+ const realDelta = nowReal - lastRealTime;
4522
+ const audioDelta = time - lastAudioTime;
4523
+ if (realDelta > 0.5 || audioDelta > 0.5) {
4524
+ console.warn(
4525
+ `[sequencer] Interruption detected (realDelta: ${realDelta.toFixed(3)}s, audioDelta: ${audioDelta.toFixed(3)}s). Stopping playback.`
4526
+ );
4527
+ stop();
4528
+ options.onEnd(true);
4529
+ return;
4530
+ }
4531
+ }
4532
+ lastRealTime = nowReal;
4533
+ lastAudioTime = time;
4383
4534
  for (const track of options.getTracks()) {
4384
4535
  trackVolumeMap.set(track.id, track.volume);
4385
4536
  }
@@ -4456,7 +4607,7 @@ var createSequencer = (options) => {
4456
4607
  const lastDuration = last?.duration ?? 0;
4457
4608
  if (nowIndex >= timeline.length && time > lastWhen + lastDuration + 0.1) {
4458
4609
  stop();
4459
- options.onEnd();
4610
+ options.onEnd(false);
4460
4611
  }
4461
4612
  }
4462
4613
  };
@@ -4497,6 +4648,8 @@ var createSequencer = (options) => {
4497
4648
  }
4498
4649
  loopBase = 0;
4499
4650
  lastPlayStep = fromStepValue - 1e-4;
4651
+ lastRealTime = -1;
4652
+ lastAudioTime = -1;
4500
4653
  intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
4501
4654
  animationId = requestAnimationFrame(animate);
4502
4655
  };
@@ -4694,11 +4847,11 @@ var DAW_CSS = `
4694
4847
  }
4695
4848
  .dtm-btn:active { transform: translate(3px,3px); box-shadow: none; }
4696
4849
  .dtm-btn:disabled { opacity: .3; cursor: default; box-shadow: none; }
4850
+ .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4697
4851
  .dtm-btn--primary { border-color: var(--dtm-primary); background: var(--dtm-primary); color: var(--dtm-pfg); }
4698
4852
  .dtm-btn--success { border-color: var(--dtm-success); background: var(--dtm-success); color: var(--c-black); }
4699
4853
  .dtm-btn--danger { border-color: var(--dtm-danger); background: var(--dtm-danger); color: var(--c-white); }
4700
4854
  .dtm-btn--accent { border-color: var(--dtm-accent); background: var(--dtm-accent); color: var(--c-black); }
4701
- .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4702
4855
  .dtm-btn--icon { padding: 0; }
4703
4856
 
4704
4857
  /* \u2500\u2500\u2500 \u30A2\u30A4\u30B3\u30F3\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
@@ -5332,6 +5485,18 @@ var DAW_CSS = `
5332
5485
  }
5333
5486
 
5334
5487
  .dtm-hidden { display: none !important; }
5488
+ /* \u8AAD\u8FBC\u6642\u306E\u63A7\u3048\u3081\u306A\u304A\u77E5\u3089\u305B\uFF08\u4F8B: \u30B7\u30F3\u30D7\u30EB\u30E2\u30FC\u30C9\u3067\u306E\u30C8\u30E9\u30C3\u30AF\u5408\u7B97\uFF09\u3002\u4E3B\u5F35\u3057\u3059\u304E\u306A\u3044 muted \u8868\u793A\u3002 */
5489
+ .dtm-load-note {
5490
+ margin: 6px 0 0;
5491
+ padding: 0 2px;
5492
+ font-family: var(--dtm-font);
5493
+ font-size: 10px;
5494
+ line-height: 1.5;
5495
+ letter-spacing: .04em;
5496
+ color: var(--dtm-muted);
5497
+ opacity: .85;
5498
+ }
5499
+ .dtm-load-note::before { content: "\u24D8 "; }
5335
5500
  .dtm-grow { flex: 1 1 auto; }
5336
5501
  .dtm-lyric-icon {
5337
5502
  flex: 0 0 auto;
@@ -5431,6 +5596,62 @@ var DAW_CSS = `
5431
5596
  white-space: nowrap;
5432
5597
  }
5433
5598
  .dtm-player-mml-link:hover { color: var(--dtm-primary); }
5599
+ .dtm-player-more-container {
5600
+ position: relative;
5601
+ display: inline-flex;
5602
+ align-items: center;
5603
+ }
5604
+ .dtm-player-more-btn {
5605
+ background: transparent;
5606
+ border: none;
5607
+ color: var(--dtm-muted);
5608
+ cursor: pointer;
5609
+ padding: 2px;
5610
+ display: flex;
5611
+ align-items: center;
5612
+ justify-content: center;
5613
+ border-radius: 50%;
5614
+ width: 24px;
5615
+ height: 24px;
5616
+ transition: color 0.15s, background-color 0.15s;
5617
+ }
5618
+ .dtm-player-more-btn:hover,
5619
+ .dtm-player-more-btn.is-active {
5620
+ color: var(--dtm-text);
5621
+ background: var(--dtm-border2);
5622
+ }
5623
+ .dtm-player-menu {
5624
+ position: absolute;
5625
+ top: calc(100% + 4px);
5626
+ right: 0;
5627
+ background: var(--dtm-deep);
5628
+ border: 2px solid var(--dtm-border2);
5629
+ box-shadow: 4px 4px 0 var(--c-black);
5630
+ z-index: 200;
5631
+ display: flex;
5632
+ flex-direction: column;
5633
+ padding: 4px 0;
5634
+ min-width: 130px;
5635
+ font-family: var(--dtm-font);
5636
+ }
5637
+ .dtm-player-menu-item {
5638
+ background: transparent;
5639
+ border: none;
5640
+ color: var(--dtm-text);
5641
+ padding: 6px 12px;
5642
+ text-align: left;
5643
+ cursor: pointer;
5644
+ font-size: 11px;
5645
+ font-family: inherit;
5646
+ white-space: nowrap;
5647
+ width: 100%;
5648
+ box-sizing: border-box;
5649
+ transition: background-color 0.1s, color 0.1s;
5650
+ }
5651
+ .dtm-player-menu-item:hover {
5652
+ background: var(--dtm-primary);
5653
+ color: var(--dtm-pfg);
5654
+ }
5434
5655
  .dtm-player-emoji {
5435
5656
  position: relative;
5436
5657
  display: inline-flex;
@@ -5706,6 +5927,75 @@ var showBalloon = (balloonEl) => {
5706
5927
  hideActiveBalloon();
5707
5928
  }, 3e3);
5708
5929
  };
5930
+ var copyToClipboard = async (doc, text) => {
5931
+ try {
5932
+ await navigator.clipboard.writeText(text);
5933
+ return true;
5934
+ } catch {
5935
+ try {
5936
+ const textarea = doc.createElement("textarea");
5937
+ textarea.value = text;
5938
+ textarea.style.position = "fixed";
5939
+ textarea.style.opacity = "0";
5940
+ doc.body.appendChild(textarea);
5941
+ textarea.select();
5942
+ const ok = doc.execCommand("copy");
5943
+ doc.body.removeChild(textarea);
5944
+ return ok;
5945
+ } catch {
5946
+ return false;
5947
+ }
5948
+ }
5949
+ };
5950
+ var toBase64Url = (bytes) => {
5951
+ let bin = "";
5952
+ for (let i = 0; i < bytes.length; i++) {
5953
+ bin += String.fromCharCode(bytes[i]);
5954
+ }
5955
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5956
+ };
5957
+ var HIRAGANA_START = 12353;
5958
+ var HIRAGANA_END = 12447;
5959
+ var KATAKANA_START = 12449;
5960
+ var KATAKANA_END = 12543;
5961
+ var PROLONGED_MARK = 12540;
5962
+ var SHIFT_KATAKANA = 255;
5963
+ var VALUE_PROLONGED = 223;
5964
+ var customEncode = (str) => {
5965
+ const bytes = [];
5966
+ for (let i = 0; i < str.length; i++) {
5967
+ const code = str.charCodeAt(i);
5968
+ if (code <= 127) {
5969
+ bytes.push(code);
5970
+ } else if (code === PROLONGED_MARK) {
5971
+ bytes.push(VALUE_PROLONGED);
5972
+ } else if (code >= HIRAGANA_START && code <= HIRAGANA_END) {
5973
+ bytes.push(128 + (code - HIRAGANA_START));
5974
+ } else if (code >= KATAKANA_START && code <= KATAKANA_END) {
5975
+ bytes.push(SHIFT_KATAKANA);
5976
+ bytes.push(128 + (code - 96 - HIRAGANA_START));
5977
+ }
5978
+ }
5979
+ return new Uint8Array(bytes);
5980
+ };
5981
+ var encodeMml = async (mml) => {
5982
+ try {
5983
+ if (typeof CompressionStream !== "undefined") {
5984
+ const cs = new CompressionStream("gzip");
5985
+ const w = cs.writable.getWriter();
5986
+ w.write(customEncode(mml));
5987
+ w.close();
5988
+ const buf = await new Response(cs.readable).arrayBuffer();
5989
+ return `z.${toBase64Url(new Uint8Array(buf))}`;
5990
+ }
5991
+ } catch (e) {
5992
+ console.warn(
5993
+ "[dtm] CompressionStream failed, fallback to encodeURIComponent",
5994
+ e
5995
+ );
5996
+ }
5997
+ return `u.${encodeURIComponent(mml)}`;
5998
+ };
5709
5999
  var mountMmlPlayer = (target, mml, options = {}) => {
5710
6000
  injectStyles(target.ownerDocument ?? document);
5711
6001
  const {
@@ -5851,6 +6141,83 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5851
6141
  emojiEls.push(em);
5852
6142
  emojiByTrack.set(index, em);
5853
6143
  }
6144
+ const menuContainer = doc.createElement("div");
6145
+ menuContainer.className = "dtm-player-more-container";
6146
+ const moreBtn = doc.createElement("button");
6147
+ moreBtn.type = "button";
6148
+ moreBtn.className = "dtm-player-more-btn";
6149
+ moreBtn.innerHTML = icon("more", 14);
6150
+ moreBtn.title = "\u30E1\u30CB\u30E5\u30FC";
6151
+ menuContainer.appendChild(moreBtn);
6152
+ const menuDropdown = doc.createElement("div");
6153
+ menuDropdown.className = "dtm-player-menu";
6154
+ menuDropdown.style.display = "none";
6155
+ const copyMmlItem = doc.createElement("button");
6156
+ copyMmlItem.type = "button";
6157
+ copyMmlItem.className = "dtm-player-menu-item";
6158
+ copyMmlItem.textContent = "MML\u30B3\u30D4\u30FC";
6159
+ const embedItem = doc.createElement("button");
6160
+ embedItem.type = "button";
6161
+ embedItem.className = "dtm-player-menu-item";
6162
+ embedItem.textContent = "\u57CB\u3081\u8FBC\u3080";
6163
+ menuDropdown.appendChild(copyMmlItem);
6164
+ menuDropdown.appendChild(embedItem);
6165
+ menuContainer.appendChild(menuDropdown);
6166
+ mmlHeader.appendChild(menuContainer);
6167
+ const toggleMenu = (show) => {
6168
+ const visible = show !== void 0 ? show : menuDropdown.style.display === "none";
6169
+ menuDropdown.style.display = visible ? "flex" : "none";
6170
+ if (visible) {
6171
+ moreBtn.classList.add("is-active");
6172
+ doc.addEventListener("click", handleOutsideClick);
6173
+ } else {
6174
+ moreBtn.classList.remove("is-active");
6175
+ doc.removeEventListener("click", handleOutsideClick);
6176
+ }
6177
+ };
6178
+ const handleOutsideClick = (e) => {
6179
+ if (!menuContainer.contains(e.target)) {
6180
+ toggleMenu(false);
6181
+ }
6182
+ };
6183
+ moreBtn.addEventListener("click", (e) => {
6184
+ e.stopPropagation();
6185
+ toggleMenu();
6186
+ });
6187
+ copyMmlItem.addEventListener("click", async (e) => {
6188
+ e.stopPropagation();
6189
+ const success = await copyToClipboard(doc, mml);
6190
+ if (success) {
6191
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F\uFF01";
6192
+ } else {
6193
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u5931\u6557";
6194
+ }
6195
+ setTimeout(() => {
6196
+ copyMmlItem.textContent = "MML\u30B3\u30D4\u30FC";
6197
+ }, 2e3);
6198
+ });
6199
+ embedItem.addEventListener("click", async (e) => {
6200
+ e.stopPropagation();
6201
+ embedItem.textContent = "\u751F\u6210\u4E2D...";
6202
+ try {
6203
+ const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6204
+ const payload = await encodeMml(mml);
6205
+ const url2 = `${embedBase}#${payload}`;
6206
+ const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6207
+ const success = await copyToClipboard(doc, snippet);
6208
+ if (success) {
6209
+ embedItem.textContent = "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F\uFF01";
6210
+ } else {
6211
+ embedItem.textContent = "\u30B3\u30D4\u30FC\u5931\u6557";
6212
+ }
6213
+ } catch (err2) {
6214
+ console.error("[dtm] failed to generate embed snippet", err2);
6215
+ embedItem.textContent = "\u751F\u6210\u5931\u6557";
6216
+ }
6217
+ setTimeout(() => {
6218
+ embedItem.textContent = "\u57CB\u3081\u8FBC\u3080";
6219
+ }, 2e3);
6220
+ });
5854
6221
  const promotedToImage = /* @__PURE__ */ new Set();
5855
6222
  for (const [index, lt] of lyricTracks) {
5856
6223
  const em = emojiByTrack.get(index);
@@ -6159,7 +6526,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6159
6526
  onTick: (step) => {
6160
6527
  renderPlayhead(step);
6161
6528
  },
6162
- onEnd: () => finish(),
6529
+ onEnd: (interrupted) => finish(),
6163
6530
  stepsPerBar: STEPS_PER_BAR
6164
6531
  });
6165
6532
  let playing = false;
@@ -6254,6 +6621,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6254
6621
  else play();
6255
6622
  });
6256
6623
  const destroy = () => {
6624
+ doc.removeEventListener("click", handleOutsideClick);
6257
6625
  seq.stop();
6258
6626
  peekVoices()?.stopStream();
6259
6627
  if (activePlayer === instance) activePlayer = null;
@@ -7172,6 +7540,10 @@ var mountDAW = (target, options = {}) => {
7172
7540
  0,
7173
7541
  Math.floor(step / snapGridSteps) * snapGridSteps
7174
7542
  );
7543
+ if (playbackState === "paused") {
7544
+ playbackState = "stopped";
7545
+ updateTransport();
7546
+ }
7175
7547
  redrawAll();
7176
7548
  });
7177
7549
  setDrawOffset(currentOffsetX, currentOffsetY);
@@ -7247,9 +7619,14 @@ var mountDAW = (target, options = {}) => {
7247
7619
  }
7248
7620
  redrawAll();
7249
7621
  },
7250
- onEnd: () => {
7251
- playbackState = "stopped";
7252
- currentPlayStep = 0;
7622
+ onEnd: (interrupted) => {
7623
+ if (interrupted) {
7624
+ playbackState = "paused";
7625
+ pausedPlayStep = currentPlayStep;
7626
+ } else {
7627
+ playbackState = "stopped";
7628
+ currentPlayStep = 0;
7629
+ }
7253
7630
  updateTransport();
7254
7631
  redrawAll();
7255
7632
  },
@@ -7339,13 +7716,13 @@ var mountDAW = (target, options = {}) => {
7339
7716
  redrawAll();
7340
7717
  };
7341
7718
  const togglePlay = () => {
7342
- if (playbackState === "playing") stop();
7719
+ if (playbackState === "playing") pause();
7343
7720
  else play();
7344
7721
  };
7345
7722
  const updateTransport = () => {
7346
7723
  const playing = playbackState === "playing";
7347
- const label = playing ? "\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7348
- refs.playBtn.innerHTML = `${icon(playing ? "stop" : "play")}<span>${label}</span>`;
7724
+ const label = playing ? "\u4E00\u6642\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7725
+ refs.playBtn.innerHTML = `${icon(playing ? "pause" : "play")}<span>${label}</span>`;
7349
7726
  refs.playBtn.classList.toggle("dtm-play--stop", playing);
7350
7727
  };
7351
7728
  const updateUndoRedo = () => {
@@ -7611,7 +7988,8 @@ var mountDAW = (target, options = {}) => {
7611
7988
  const metaLine = formatMmlMeta({
7612
7989
  instrument: currentInstrument || void 0,
7613
7990
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
7614
- volume: masterVolume
7991
+ volume: masterVolume,
7992
+ mode
7615
7993
  });
7616
7994
  if (refs.decomposeChordToggle.checked) {
7617
7995
  const ignoreHeavy = refs.ignoreChordHeavyToggle.checked;
@@ -7733,20 +8111,25 @@ var mountDAW = (target, options = {}) => {
7733
8111
  };
7734
8112
  const loadMML = (mml) => {
7735
8113
  if (!mml) return;
8114
+ stop();
7736
8115
  clearAll();
7737
8116
  for (const t of trackStates) t.core.setLoadMode(true);
7738
8117
  const {
7739
8118
  placements,
7740
8119
  bpm: parsedBpm,
7741
8120
  lyrics,
7742
- meta
8121
+ meta,
8122
+ mergedTrackCount
7743
8123
  } = parseMML(mml, {
7744
8124
  stepsPerBar: renderConfig.stepsPerBar,
7745
8125
  collectLyrics: true,
7746
8126
  // このDAWのトラック数を超えるチャンネルはベースへ畳み込む(従来挙動)
7747
8127
  clampTrackCount: trackStates.length
7748
8128
  });
7749
- currentInstrument = meta.instrument ?? "";
8129
+ if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
8130
+ currentInstrument = meta.instrument;
8131
+ options.onInstrumentChange?.(meta.instrument);
8132
+ }
7750
8133
  if (meta.drum && drumPatterns[meta.drum]) {
7751
8134
  currentDrumPattern = meta.drum;
7752
8135
  refs.drumSelect.value = meta.drum;
@@ -7798,6 +8181,13 @@ var mountDAW = (target, options = {}) => {
7798
8181
  redrawAll();
7799
8182
  updateTrackPanel();
7800
8183
  updateUndoRedo();
8184
+ if (!isAdvanced && mergedTrackCount > 0) {
8185
+ refs.mmlLoadNote.textContent = "\u30B7\u30F3\u30D7\u30EB\u30E2\u30FC\u30C9\u306E\u305F\u3081\u3001\u4E00\u90E8\u306E\u30C8\u30E9\u30C3\u30AF\u3092\u5408\u7B97\u3057\u3066\u8AAD\u307F\u8FBC\u307F\u307E\u3057\u305F";
8186
+ refs.mmlLoadNote.classList.remove("dtm-hidden");
8187
+ } else {
8188
+ refs.mmlLoadNote.textContent = "";
8189
+ refs.mmlLoadNote.classList.add("dtm-hidden");
8190
+ }
7801
8191
  };
7802
8192
  const applyChord = () => {
7803
8193
  const active = getActive();
@@ -7830,6 +8220,7 @@ var mountDAW = (target, options = {}) => {
7830
8220
  applyMidiSelection(midi, selected);
7831
8221
  };
7832
8222
  const applyMidiSelection = (midi, selectedIndices) => {
8223
+ stop();
7833
8224
  clearAll();
7834
8225
  for (const t of trackStates) t.core.setLoadMode(true);
7835
8226
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -8261,6 +8652,25 @@ var mountDAW = (target, options = {}) => {
8261
8652
  const setLoading = (loading) => {
8262
8653
  refs.topbar.classList.toggle("is-loading", loading);
8263
8654
  };
8655
+ const getCurrentPlayStep = () => {
8656
+ if (playbackState === "playing") return currentPlayStep;
8657
+ if (playbackState === "paused") return pausedPlayStep;
8658
+ return playStartStep;
8659
+ };
8660
+ const forcePauseAt = (step) => {
8661
+ playStartStep = step;
8662
+ pausedPlayStep = step;
8663
+ currentPlayStep = step;
8664
+ playbackState = "paused";
8665
+ const canvas = getGridCanvas();
8666
+ currentOffsetX = Math.max(
8667
+ 0,
8668
+ step * renderConfig.stepWidth - canvas.width * 0.5
8669
+ );
8670
+ setDrawOffset(currentOffsetX, currentOffsetY);
8671
+ updateTransport();
8672
+ redrawAll();
8673
+ };
8264
8674
  return {
8265
8675
  play,
8266
8676
  pause,
@@ -8298,9 +8708,12 @@ var mountDAW = (target, options = {}) => {
8298
8708
  exportMIDI: exportMIDI2,
8299
8709
  setBpm,
8300
8710
  getPlaybackState: () => playbackState,
8711
+ getCurrentPlayStep,
8712
+ forcePauseAt,
8301
8713
  setLoading,
8302
8714
  destroy: () => {
8303
8715
  sequencer.stop();
8716
+ options.singingVoices?.stopStream();
8304
8717
  resizeObserver.disconnect();
8305
8718
  document.removeEventListener("pointermove", onPointerMove);
8306
8719
  document.removeEventListener("pointerup", onPointerUp);
@@ -8310,110 +8723,6 @@ var mountDAW = (target, options = {}) => {
8310
8723
  };
8311
8724
  };
8312
8725
 
8313
- // src/instrument-presets.ts
8314
- var INSTRUMENT_PRESETS = {
8315
- // --- STANDARD: 汎用性と完成度重視 ---
8316
- piano: {
8317
- displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
8318
- description: "\u6700\u3082\u7834\u7DBB\u3057\u306B\u304F\u3044\u69CB\u6210\u3002\u697D\u66F2\u5236\u4F5C\u306E\u30B9\u30B1\u30C3\u30C1\u306B\u3082\u6700\u9069\u3002",
8319
- melody: "Acoustic Grand Piano",
8320
- submelody: "Vibraphone",
8321
- bass: "Electric Bass (finger)",
8322
- chord: "Pad 2 (warm)"
8323
- },
8324
- acoustic: {
8325
- displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
8326
- description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
8327
- melody: "Acoustic Guitar (steel)",
8328
- submelody: "Harmonica",
8329
- bass: "Acoustic Bass",
8330
- chord: "Acoustic Guitar (nylon)"
8331
- },
8332
- jazz_night: {
8333
- displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
8334
- description: "Rhodes\u98A8\u306EEP\u3068\u30A6\u30C3\u30C9\u30D9\u30FC\u30B9\u306B\u3088\u308B\u3001\u5927\u4EBA\u3073\u305F\u30A2\u30F3\u30B5\u30F3\u30D6\u30EB\u3002",
8335
- melody: "Electric Piano 1",
8336
- submelody: "Flute",
8337
- bass: "Acoustic Bass",
8338
- chord: "Electric Guitar (jazz)"
8339
- },
8340
- // --- MODERN & VIBE: エッジの効いた現代的な響き ---
8341
- synth_pop: {
8342
- displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
8343
- description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
8344
- melody: "Lead 2 (sawtooth)",
8345
- submelody: "Lead 4 (chiff)",
8346
- bass: "Synth Bass 2",
8347
- chord: "Pad 3 (polysynth)"
8348
- },
8349
- cyber_punk: {
8350
- displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
8351
- description: "\u30C7\u30B8\u30BF\u30EB\u306A\u51B7\u305F\u3055\u3068\u6B6A\u307F\u304C\u6DF7\u3056\u308A\u5408\u3046\u3001\u672A\u6765\u7684\u306A\u97FF\u304D\u3002",
8352
- melody: "Lead 8 (bass + lead)",
8353
- submelody: "Lead 5 (charang)",
8354
- bass: "Synth Bass 2",
8355
- chord: "Pad 8 (sweep)"
8356
- },
8357
- rock: {
8358
- displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
8359
- description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
8360
- melody: "Distortion Guitar",
8361
- submelody: "Rock Organ",
8362
- bass: "Electric Bass (pick)",
8363
- chord: "Overdriven Guitar"
8364
- },
8365
- // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
8366
- orchestra: {
8367
- displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
8368
- description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
8369
- melody: "French Horn",
8370
- submelody: "Pizzicato Strings",
8371
- bass: "Cello",
8372
- chord: "Tremolo Strings"
8373
- },
8374
- japanese_wa: {
8375
- displayName: "\u548C\u98A8\u30FB\u96C5",
8376
- description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
8377
- melody: "Koto",
8378
- submelody: "Shamisen",
8379
- bass: "Taiko Drum",
8380
- chord: "Shakuhachi"
8381
- },
8382
- arabic_exotic: {
8383
- displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
8384
- description: "\u30B7\u30BF\u30FC\u30EB\u3084\u30D0\u30B0\u30D1\u30A4\u30D7\u306B\u3088\u308B\u3001\u7570\u56FD\u60C5\u7DD2\u6EA2\u308C\u308B\u30B5\u30A6\u30F3\u30C9\u3002",
8385
- melody: "Sitar",
8386
- submelody: "Bagpipe",
8387
- bass: "Fretless Bass",
8388
- chord: "Kalimba"
8389
- },
8390
- // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
8391
- fantasy_rpg: {
8392
- displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
8393
- description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
8394
- melody: "Ocarina",
8395
- submelody: "Celesta",
8396
- bass: "Timpani",
8397
- chord: "Orchestral Harp"
8398
- },
8399
- ambient_cloud: {
8400
- displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
8401
- description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
8402
- melody: "Lead 6 (voice)",
8403
- submelody: "Music Box",
8404
- bass: "Synth Bass 1",
8405
- chord: "Pad 7 (halo)"
8406
- },
8407
- retro_game: {
8408
- displayName: "8-bit \u30EC\u30C8\u30ED",
8409
- description: "\u77E9\u5F62\u6CE2\u3092\u60F3\u8D77\u3055\u305B\u308B\u3001\u521D\u671F\u30B2\u30FC\u30E0\u6A5F\u306E\u3088\u3046\u306A\u61D0\u304B\u3057\u3044\u97FF\u304D\u3002",
8410
- melody: "Lead 1 (square)",
8411
- submelody: "Lead 2 (sawtooth)",
8412
- bass: "Synth Bass 1",
8413
- chord: "Clavinet"
8414
- }
8415
- };
8416
-
8417
8726
  // src/headless-player.ts
8418
8727
  var STEPS_PER_BAR2 = 192;
8419
8728
  var playMML = (mml, options = {}) => {
@@ -8464,7 +8773,7 @@ var playMML = (mml, options = {}) => {
8464
8773
  },
8465
8774
  onTick: () => {
8466
8775
  },
8467
- onEnd: () => finish(),
8776
+ onEnd: (interrupted) => finish(),
8468
8777
  stepsPerBar: STEPS_PER_BAR2
8469
8778
  });
8470
8779
  const finish = () => {
@@ -9157,32 +9466,56 @@ var createDtmStudio = async (options = {}) => {
9157
9466
  return p;
9158
9467
  };
9159
9468
  const defaultPreset = options.defaultPreset ?? "retro_game";
9469
+ const getRoleForTrackIndex = (idx, mode = "simple") => {
9470
+ if (mode === "simple") {
9471
+ if (idx === 0) return "melody";
9472
+ if (idx === 1) return "submelody";
9473
+ if (idx === 2) return "bass";
9474
+ return "chord";
9475
+ } else {
9476
+ return TRACK_ROLES[idx] ?? `t${idx}`;
9477
+ }
9478
+ };
9479
+ const getRoleFromTrackId = (trackId, mode = "simple") => {
9480
+ if (trackId === "melody" || trackId === "submelody" || trackId === "bass" || trackId === "chord") {
9481
+ return trackId;
9482
+ }
9483
+ if (trackId.startsWith("t")) {
9484
+ const idx = Number(trackId.substring(1));
9485
+ if (!isNaN(idx)) {
9486
+ return getRoleForTrackIndex(idx, mode);
9487
+ }
9488
+ }
9489
+ return trackId;
9490
+ };
9160
9491
  const instrumentNameFor = (preset, trackId) => preset[trackId] ?? preset.melody;
9161
- const resolveSoundFont = (presetKey, trackId) => {
9492
+ const resolveSoundFont = (presetKey, trackId, mode = "simple") => {
9162
9493
  const preset = INSTRUMENT_PRESETS[presetKey];
9163
9494
  if (!preset) return void 0;
9164
- const key = nameToKey[instrumentNameFor(preset, trackId)];
9495
+ const role = getRoleFromTrackId(trackId, mode);
9496
+ const key = nameToKey[instrumentNameFor(preset, role)];
9165
9497
  return key ? soundFonts.get(key) : void 0;
9166
9498
  };
9167
- const loadPreset = async (presetKey, trackIds = [...TRACK_ROLES]) => {
9499
+ const loadPreset = async (presetKey, trackIds = [...TRACK_ROLES], mode = "simple") => {
9168
9500
  const preset = INSTRUMENT_PRESETS[presetKey];
9169
9501
  if (!preset) return;
9170
9502
  await listReady;
9171
9503
  const keys = /* @__PURE__ */ new Set();
9172
9504
  for (const trackId of trackIds) {
9173
- const key = nameToKey[instrumentNameFor(preset, trackId)];
9505
+ const role = getRoleFromTrackId(trackId, mode);
9506
+ const key = nameToKey[instrumentNameFor(preset, role)];
9174
9507
  if (key) keys.add(key);
9175
9508
  }
9176
9509
  await Promise.all([...keys].map((key) => loadInstrument(key)));
9177
9510
  };
9178
- const applyPreset = async (daw, presetKey, trackIds, loadingTarget) => {
9511
+ const applyPreset = async (daw, presetKey, trackIds, loadingTarget, mode = "simple") => {
9179
9512
  const wasPlaying = daw.getPlaybackState() === "playing";
9180
9513
  if (wasPlaying) daw.pause();
9181
9514
  const overlay = loadingTarget ? showLoadingOverlay(loadingTarget) : null;
9182
9515
  daw.setLoading?.(true);
9183
9516
  try {
9184
9517
  daw.setInstrument(presetKey);
9185
- await loadPreset(presetKey, trackIds);
9518
+ await loadPreset(presetKey, trackIds, mode);
9186
9519
  } finally {
9187
9520
  overlay?.remove();
9188
9521
  daw.setLoading?.(false);
@@ -9217,8 +9550,10 @@ var createDtmStudio = async (options = {}) => {
9217
9550
  const key = select.value;
9218
9551
  opts.onChange?.(key);
9219
9552
  const trackIds = opts.getTrackIds?.() ?? [...TRACK_ROLES];
9553
+ const isAdvanced = trackIds.includes("t0");
9554
+ const mode = isAdvanced ? "advanced" : "simple";
9220
9555
  try {
9221
- await applyPreset(daw, key, trackIds, opts.loadingTarget);
9556
+ await applyPreset(daw, key, trackIds, opts.loadingTarget, mode);
9222
9557
  } finally {
9223
9558
  busy = false;
9224
9559
  }
@@ -9259,13 +9594,20 @@ var createDtmStudio = async (options = {}) => {
9259
9594
  const mountedPlayers = [];
9260
9595
  const mountedModeSwitches = [];
9261
9596
  const mountEditor = (target, opts = {}) => {
9262
- const { preset, presetUI, ...dawOverrides } = opts;
9597
+ const { preset, presetUI, onInstrumentChange, ...dawOverrides } = opts;
9263
9598
  const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
9264
9599
  const trackIds = tracks.map((t) => t.id);
9265
9600
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
9266
- let editorPreset = presetKey;
9601
+ const meta = opts.initialMML ? parseMmlMeta(opts.initialMML) : {};
9602
+ const initialPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : presetKey;
9603
+ let editorPreset = initialPreset;
9604
+ const isAdvancedMode = dawOverrides.mode === "advanced";
9267
9605
  const playNote = (e) => {
9268
- const sf = resolveSoundFont(editorPreset, e.trackId);
9606
+ const sf = resolveSoundFont(
9607
+ editorPreset,
9608
+ e.trackId,
9609
+ isAdvancedMode ? "advanced" : "simple"
9610
+ );
9269
9611
  if (!sf) return;
9270
9612
  sf.play({
9271
9613
  ctx: audioCtx,
@@ -9276,6 +9618,14 @@ var createDtmStudio = async (options = {}) => {
9276
9618
  duration: e.duration
9277
9619
  });
9278
9620
  };
9621
+ let presetSelect = null;
9622
+ const handleInstrumentChange = (key) => {
9623
+ editorPreset = key;
9624
+ if (presetSelect) {
9625
+ presetSelect.setValue(key);
9626
+ }
9627
+ onInstrumentChange?.(key);
9628
+ };
9279
9629
  const base = {
9280
9630
  getAudioTime: () => audioCtx.currentTime,
9281
9631
  onResumeAudio: resumeAudio,
@@ -9284,19 +9634,19 @@ var createDtmStudio = async (options = {}) => {
9284
9634
  singingVoices,
9285
9635
  parseMidi,
9286
9636
  onToggleRecord,
9637
+ onInstrumentChange: handleInstrumentChange,
9287
9638
  ...dawOverrides
9288
9639
  };
9289
9640
  const daw = mountDAW(target, base);
9290
9641
  mountedEditors.push(daw);
9291
9642
  const wantPresetUI = presetUI ?? features.presetUI;
9292
- let presetSelect = null;
9293
9643
  if (wantPresetUI) {
9294
9644
  editorPresetSelects.get(target)?.destroy();
9295
9645
  const rollEl = target.querySelector('[data-dtm="roll"]');
9296
9646
  presetSelect = mountPresetSelect(target, {
9297
9647
  getDaw: () => daw,
9298
9648
  getTrackIds: () => trackIds,
9299
- value: presetKey,
9649
+ value: initialPreset,
9300
9650
  loadingTarget: rollEl ?? target,
9301
9651
  position: "prepend",
9302
9652
  // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
@@ -9306,9 +9656,13 @@ var createDtmStudio = async (options = {}) => {
9306
9656
  });
9307
9657
  editorPresetSelects.set(target, presetSelect);
9308
9658
  }
9309
- daw.setInstrument(presetKey);
9659
+ daw.setInstrument(initialPreset);
9310
9660
  daw.setLoading?.(true);
9311
- void loadPreset(presetKey, trackIds).finally(() => {
9661
+ void loadPreset(
9662
+ initialPreset,
9663
+ trackIds,
9664
+ isAdvancedMode ? "advanced" : "simple"
9665
+ ).finally(() => {
9312
9666
  daw.setLoading?.(false);
9313
9667
  });
9314
9668
  const destroy = () => {
@@ -9319,7 +9673,17 @@ var createDtmStudio = async (options = {}) => {
9319
9673
  const i = mountedEditors.indexOf(daw);
9320
9674
  if (i >= 0) mountedEditors.splice(i, 1);
9321
9675
  };
9322
- return { ...daw, destroy };
9676
+ return {
9677
+ ...daw,
9678
+ setInstrument: (name) => {
9679
+ daw.setInstrument(name);
9680
+ editorPreset = name;
9681
+ if (presetSelect) {
9682
+ presetSelect.setValue(name);
9683
+ }
9684
+ },
9685
+ destroy
9686
+ };
9323
9687
  };
9324
9688
  const mountModeSwitch = (target, opts) => {
9325
9689
  const doc = target.ownerDocument;
@@ -9409,15 +9773,30 @@ var createDtmStudio = async (options = {}) => {
9409
9773
  const parsed = parseMML(mml, {});
9410
9774
  const meta = parsed.meta ?? {};
9411
9775
  const playerPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : defaultPreset;
9776
+ const isAdvancedMode = meta.mode === "advanced";
9412
9777
  const trackIndices = [
9413
9778
  ...new Set(parsed.placements.map((p) => p.trackIndex))
9414
9779
  ];
9415
- const trackIds = trackIndices.map((idx) => TRACK_ROLES[idx] ?? `t${idx}`);
9780
+ const trackIds = trackIndices.map(
9781
+ (idx) => getRoleForTrackIndex(idx, isAdvancedMode ? "advanced" : "simple")
9782
+ );
9416
9783
  const loadTrackIds = trackIds.length > 0 ? trackIds : [...TRACK_ROLES];
9417
- void loadPreset(playerPreset, loadTrackIds);
9784
+ void loadPreset(
9785
+ playerPreset,
9786
+ loadTrackIds,
9787
+ isAdvancedMode ? "advanced" : "simple"
9788
+ );
9418
9789
  const playPlayerNote = (e) => {
9419
- const role = TRACK_ROLES[Number(e.trackId)] ?? `t${e.trackId}`;
9420
- const sf = resolveSoundFont(playerPreset, role);
9790
+ const idx = Number(e.trackId);
9791
+ const role = getRoleForTrackIndex(
9792
+ idx,
9793
+ isAdvancedMode ? "advanced" : "simple"
9794
+ );
9795
+ const sf = resolveSoundFont(
9796
+ playerPreset,
9797
+ role,
9798
+ isAdvancedMode ? "advanced" : "simple"
9799
+ );
9421
9800
  if (!sf) return;
9422
9801
  sf.play({
9423
9802
  ctx: audioCtx,