@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.mjs CHANGED
@@ -1107,6 +1107,9 @@ var ICONS = {
1107
1107
  },
1108
1108
  info: {
1109
1109
  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"
1110
+ },
1111
+ more: {
1112
+ 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"
1110
1113
  }
1111
1114
  };
1112
1115
  var icon = (name, size = 20) => {
@@ -1233,6 +1236,7 @@ var buildUI = (target, options) => {
1233
1236
  <textarea class="dtm-textarea dtm-grow" data-dtm="mml-input" placeholder="MML\u3092\u5165\u529B"></textarea>
1234
1237
  <button class="dtm-btn dtm-btn--primary" data-dtm="mml-load">\u8AAD\u8FBC</button>
1235
1238
  </div>
1239
+ <p class="dtm-load-note dtm-hidden" data-dtm="mml-load-note"></p>
1236
1240
  </div>
1237
1241
  </details>
1238
1242
 
@@ -1358,6 +1362,7 @@ var buildUI = (target, options) => {
1358
1362
  midiPanel: sel("midi-panel"),
1359
1363
  mmlInput: sel("mml-input"),
1360
1364
  mmlLoadBtn: sel("mml-load"),
1365
+ mmlLoadNote: sel("mml-load-note"),
1361
1366
  shiftSelect: sel("shift-select"),
1362
1367
  shiftApplyBtn: sel("shift-apply"),
1363
1368
  macroClear: sel("macro-clear"),
@@ -1511,6 +1516,110 @@ var DRUM_PATTERNS = {
1511
1516
  ]
1512
1517
  };
1513
1518
 
1519
+ // src/instrument-presets.ts
1520
+ var INSTRUMENT_PRESETS = {
1521
+ // --- STANDARD: 汎用性と完成度重視 ---
1522
+ piano: {
1523
+ displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
1524
+ 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",
1525
+ melody: "Acoustic Grand Piano",
1526
+ submelody: "Vibraphone",
1527
+ bass: "Electric Bass (finger)",
1528
+ chord: "Pad 2 (warm)"
1529
+ },
1530
+ acoustic: {
1531
+ displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
1532
+ description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
1533
+ melody: "Acoustic Guitar (steel)",
1534
+ submelody: "Harmonica",
1535
+ bass: "Acoustic Bass",
1536
+ chord: "Acoustic Guitar (nylon)"
1537
+ },
1538
+ jazz_night: {
1539
+ displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
1540
+ 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",
1541
+ melody: "Electric Piano 1",
1542
+ submelody: "Flute",
1543
+ bass: "Acoustic Bass",
1544
+ chord: "Electric Guitar (jazz)"
1545
+ },
1546
+ // --- MODERN & VIBE: エッジの効いた現代的な響き ---
1547
+ synth_pop: {
1548
+ displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
1549
+ description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
1550
+ melody: "Lead 2 (sawtooth)",
1551
+ submelody: "Lead 4 (chiff)",
1552
+ bass: "Synth Bass 2",
1553
+ chord: "Pad 3 (polysynth)"
1554
+ },
1555
+ cyber_punk: {
1556
+ displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
1557
+ 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",
1558
+ melody: "Lead 8 (bass + lead)",
1559
+ submelody: "Lead 5 (charang)",
1560
+ bass: "Synth Bass 2",
1561
+ chord: "Pad 8 (sweep)"
1562
+ },
1563
+ rock: {
1564
+ displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
1565
+ description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
1566
+ melody: "Distortion Guitar",
1567
+ submelody: "Rock Organ",
1568
+ bass: "Electric Bass (pick)",
1569
+ chord: "Overdriven Guitar"
1570
+ },
1571
+ // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
1572
+ orchestra: {
1573
+ displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
1574
+ description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
1575
+ melody: "French Horn",
1576
+ submelody: "Pizzicato Strings",
1577
+ bass: "Cello",
1578
+ chord: "Tremolo Strings"
1579
+ },
1580
+ japanese_wa: {
1581
+ displayName: "\u548C\u98A8\u30FB\u96C5",
1582
+ description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
1583
+ melody: "Koto",
1584
+ submelody: "Shamisen",
1585
+ bass: "Taiko Drum",
1586
+ chord: "Shakuhachi"
1587
+ },
1588
+ arabic_exotic: {
1589
+ displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
1590
+ 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",
1591
+ melody: "Sitar",
1592
+ submelody: "Bagpipe",
1593
+ bass: "Fretless Bass",
1594
+ chord: "Kalimba"
1595
+ },
1596
+ // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
1597
+ fantasy_rpg: {
1598
+ displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
1599
+ description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
1600
+ melody: "Ocarina",
1601
+ submelody: "Celesta",
1602
+ bass: "Timpani",
1603
+ chord: "Orchestral Harp"
1604
+ },
1605
+ ambient_cloud: {
1606
+ displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
1607
+ description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
1608
+ melody: "Lead 6 (voice)",
1609
+ submelody: "Music Box",
1610
+ bass: "Synth Bass 1",
1611
+ chord: "Pad 7 (halo)"
1612
+ },
1613
+ retro_game: {
1614
+ displayName: "8-bit \u30EC\u30C8\u30ED",
1615
+ 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",
1616
+ melody: "Lead 1 (square)",
1617
+ submelody: "Lead 2 (sawtooth)",
1618
+ bass: "Synth Bass 1",
1619
+ chord: "Clavinet"
1620
+ }
1621
+ };
1622
+
1514
1623
  // node_modules/.pnpm/@onjmin+koe@1.0.3/node_modules/@onjmin/koe/dist/index.js
1515
1624
  var MAGIC = 1263486208;
1516
1625
  function parseKoeHeader(headerBytes) {
@@ -3919,7 +4028,7 @@ var PITCH_MAP2 = {
3919
4028
  b: 11
3920
4029
  };
3921
4030
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3922
- var META_DIRECTIVE = /#(inst|drum|volume)=([\w-]+)/gi;
4031
+ var META_DIRECTIVE = /#(inst|drum|volume|mode)=([\w-]+)/gi;
3923
4032
  var parseMmlMeta = (mml) => {
3924
4033
  const meta = {};
3925
4034
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -3929,6 +4038,10 @@ var parseMmlMeta = (mml) => {
3929
4038
  else if (key === "volume") {
3930
4039
  const v = Number.parseInt(m[2], 10);
3931
4040
  if (!Number.isNaN(v)) meta.volume = v;
4041
+ } else if (key === "mode") {
4042
+ if (m[2] === "simple" || m[2] === "advanced") {
4043
+ meta.mode = m[2];
4044
+ }
3932
4045
  }
3933
4046
  }
3934
4047
  return meta;
@@ -3939,6 +4052,7 @@ var formatMmlMeta = (meta) => {
3939
4052
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
3940
4053
  if (meta.drum) parts.push(`#drum=${meta.drum}`);
3941
4054
  if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
4055
+ if (meta.mode) parts.push(`#mode=${meta.mode}`);
3942
4056
  return parts.join(" ");
3943
4057
  };
3944
4058
  var parseMML = (mml, options = {}) => {
@@ -3955,6 +4069,7 @@ var parseMML = (mml, options = {}) => {
3955
4069
  bpm,
3956
4070
  tokenTracks: collectTokens ? tokenTracks : void 0,
3957
4071
  lyrics: collectLyrics ? /* @__PURE__ */ new Map() : void 0,
4072
+ mergedTrackCount: 0,
3958
4073
  meta: {}
3959
4074
  };
3960
4075
  }
@@ -3967,14 +4082,26 @@ var parseMML = (mml, options = {}) => {
3967
4082
  const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
3968
4083
  const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
3969
4084
  let trackIndex = 0;
4085
+ let sourceTrackIndex = 0;
3970
4086
  let octave = 4;
3971
4087
  let currentStep = 0;
3972
4088
  let baseLength = 16;
4089
+ const contributors = /* @__PURE__ */ new Map();
4090
+ const recordContributor = () => {
4091
+ let set = contributors.get(trackIndex);
4092
+ if (!set) {
4093
+ set = /* @__PURE__ */ new Set();
4094
+ contributors.set(trackIndex, set);
4095
+ }
4096
+ set.add(sourceTrackIndex);
4097
+ };
3973
4098
  for (const rawPart of parts) {
3974
4099
  const part = rawPart.trim();
3975
4100
  if (part.startsWith("@")) {
3976
4101
  let idx = Number.parseInt(part.substring(1), 10);
3977
- if (clampTrackCount !== void 0 && idx >= clampTrackCount) idx = 2;
4102
+ sourceTrackIndex = idx;
4103
+ if (clampTrackCount !== void 0 && idx >= clampTrackCount)
4104
+ idx = clampTrackCount - 1;
3978
4105
  trackIndex = idx;
3979
4106
  octave = 4;
3980
4107
  currentStep = 0;
@@ -4096,6 +4223,7 @@ var parseMML = (mml, options = {}) => {
4096
4223
  }
4097
4224
  if (j < body.length && body[j] === "]") j++;
4098
4225
  const steps = parseLength();
4226
+ if (chordNotes.length > 0) recordContributor();
4099
4227
  for (const p of chordNotes) {
4100
4228
  placements.push({
4101
4229
  trackIndex,
@@ -4119,6 +4247,7 @@ var parseMML = (mml, options = {}) => {
4119
4247
  }
4120
4248
  const midiPitch = (octave + 1) * 12 + pitch;
4121
4249
  const steps = parseLength();
4250
+ recordContributor();
4122
4251
  placements.push({
4123
4252
  trackIndex,
4124
4253
  startStep: currentStep,
@@ -4132,11 +4261,16 @@ var parseMML = (mml, options = {}) => {
4132
4261
  }
4133
4262
  }
4134
4263
  }
4264
+ let mergedTrackCount = 0;
4265
+ for (const set of contributors.values()) {
4266
+ if (set.size >= 2) mergedTrackCount++;
4267
+ }
4135
4268
  return {
4136
4269
  placements,
4137
4270
  bpm,
4138
4271
  tokenTracks: collectTokens ? tokenTracks : void 0,
4139
4272
  lyrics,
4273
+ mergedTrackCount,
4140
4274
  meta
4141
4275
  };
4142
4276
  };
@@ -4198,6 +4332,8 @@ var createSequencer = (options) => {
4198
4332
  let active = false;
4199
4333
  let fromStepValue = 0;
4200
4334
  let trackVolumeMap = /* @__PURE__ */ new Map();
4335
+ let lastRealTime = -1;
4336
+ let lastAudioTime = -1;
4201
4337
  let isLooping = false;
4202
4338
  let loopStartStep = 0;
4203
4339
  let loopEndStep = 0;
@@ -4271,6 +4407,21 @@ var createSequencer = (options) => {
4271
4407
  const sps = secondsPerStep();
4272
4408
  const time = options.getAudioTime() - startTime;
4273
4409
  const soloId = options.getSoloTrackId();
4410
+ const nowReal = performance.now() / 1e3;
4411
+ if (lastRealTime > 0 && lastAudioTime >= 0) {
4412
+ const realDelta = nowReal - lastRealTime;
4413
+ const audioDelta = time - lastAudioTime;
4414
+ if (realDelta > 0.5 || audioDelta > 0.5) {
4415
+ console.warn(
4416
+ `[sequencer] Interruption detected (realDelta: ${realDelta.toFixed(3)}s, audioDelta: ${audioDelta.toFixed(3)}s). Stopping playback.`
4417
+ );
4418
+ stop();
4419
+ options.onEnd(true);
4420
+ return;
4421
+ }
4422
+ }
4423
+ lastRealTime = nowReal;
4424
+ lastAudioTime = time;
4274
4425
  for (const track of options.getTracks()) {
4275
4426
  trackVolumeMap.set(track.id, track.volume);
4276
4427
  }
@@ -4347,7 +4498,7 @@ var createSequencer = (options) => {
4347
4498
  const lastDuration = last?.duration ?? 0;
4348
4499
  if (nowIndex >= timeline.length && time > lastWhen + lastDuration + 0.1) {
4349
4500
  stop();
4350
- options.onEnd();
4501
+ options.onEnd(false);
4351
4502
  }
4352
4503
  }
4353
4504
  };
@@ -4388,6 +4539,8 @@ var createSequencer = (options) => {
4388
4539
  }
4389
4540
  loopBase = 0;
4390
4541
  lastPlayStep = fromStepValue - 1e-4;
4542
+ lastRealTime = -1;
4543
+ lastAudioTime = -1;
4391
4544
  intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
4392
4545
  animationId = requestAnimationFrame(animate);
4393
4546
  };
@@ -4585,11 +4738,11 @@ var DAW_CSS = `
4585
4738
  }
4586
4739
  .dtm-btn:active { transform: translate(3px,3px); box-shadow: none; }
4587
4740
  .dtm-btn:disabled { opacity: .3; cursor: default; box-shadow: none; }
4741
+ .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4588
4742
  .dtm-btn--primary { border-color: var(--dtm-primary); background: var(--dtm-primary); color: var(--dtm-pfg); }
4589
4743
  .dtm-btn--success { border-color: var(--dtm-success); background: var(--dtm-success); color: var(--c-black); }
4590
4744
  .dtm-btn--danger { border-color: var(--dtm-danger); background: var(--dtm-danger); color: var(--c-white); }
4591
4745
  .dtm-btn--accent { border-color: var(--dtm-accent); background: var(--dtm-accent); color: var(--c-black); }
4592
- .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4593
4746
  .dtm-btn--icon { padding: 0; }
4594
4747
 
4595
4748
  /* \u2500\u2500\u2500 \u30A2\u30A4\u30B3\u30F3\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
@@ -5223,6 +5376,18 @@ var DAW_CSS = `
5223
5376
  }
5224
5377
 
5225
5378
  .dtm-hidden { display: none !important; }
5379
+ /* \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 */
5380
+ .dtm-load-note {
5381
+ margin: 6px 0 0;
5382
+ padding: 0 2px;
5383
+ font-family: var(--dtm-font);
5384
+ font-size: 10px;
5385
+ line-height: 1.5;
5386
+ letter-spacing: .04em;
5387
+ color: var(--dtm-muted);
5388
+ opacity: .85;
5389
+ }
5390
+ .dtm-load-note::before { content: "\u24D8 "; }
5226
5391
  .dtm-grow { flex: 1 1 auto; }
5227
5392
  .dtm-lyric-icon {
5228
5393
  flex: 0 0 auto;
@@ -5322,6 +5487,62 @@ var DAW_CSS = `
5322
5487
  white-space: nowrap;
5323
5488
  }
5324
5489
  .dtm-player-mml-link:hover { color: var(--dtm-primary); }
5490
+ .dtm-player-more-container {
5491
+ position: relative;
5492
+ display: inline-flex;
5493
+ align-items: center;
5494
+ }
5495
+ .dtm-player-more-btn {
5496
+ background: transparent;
5497
+ border: none;
5498
+ color: var(--dtm-muted);
5499
+ cursor: pointer;
5500
+ padding: 2px;
5501
+ display: flex;
5502
+ align-items: center;
5503
+ justify-content: center;
5504
+ border-radius: 50%;
5505
+ width: 24px;
5506
+ height: 24px;
5507
+ transition: color 0.15s, background-color 0.15s;
5508
+ }
5509
+ .dtm-player-more-btn:hover,
5510
+ .dtm-player-more-btn.is-active {
5511
+ color: var(--dtm-text);
5512
+ background: var(--dtm-border2);
5513
+ }
5514
+ .dtm-player-menu {
5515
+ position: absolute;
5516
+ top: calc(100% + 4px);
5517
+ right: 0;
5518
+ background: var(--dtm-deep);
5519
+ border: 2px solid var(--dtm-border2);
5520
+ box-shadow: 4px 4px 0 var(--c-black);
5521
+ z-index: 200;
5522
+ display: flex;
5523
+ flex-direction: column;
5524
+ padding: 4px 0;
5525
+ min-width: 130px;
5526
+ font-family: var(--dtm-font);
5527
+ }
5528
+ .dtm-player-menu-item {
5529
+ background: transparent;
5530
+ border: none;
5531
+ color: var(--dtm-text);
5532
+ padding: 6px 12px;
5533
+ text-align: left;
5534
+ cursor: pointer;
5535
+ font-size: 11px;
5536
+ font-family: inherit;
5537
+ white-space: nowrap;
5538
+ width: 100%;
5539
+ box-sizing: border-box;
5540
+ transition: background-color 0.1s, color 0.1s;
5541
+ }
5542
+ .dtm-player-menu-item:hover {
5543
+ background: var(--dtm-primary);
5544
+ color: var(--dtm-pfg);
5545
+ }
5325
5546
  .dtm-player-emoji {
5326
5547
  position: relative;
5327
5548
  display: inline-flex;
@@ -5597,6 +5818,75 @@ var showBalloon = (balloonEl) => {
5597
5818
  hideActiveBalloon();
5598
5819
  }, 3e3);
5599
5820
  };
5821
+ var copyToClipboard = async (doc, text) => {
5822
+ try {
5823
+ await navigator.clipboard.writeText(text);
5824
+ return true;
5825
+ } catch {
5826
+ try {
5827
+ const textarea = doc.createElement("textarea");
5828
+ textarea.value = text;
5829
+ textarea.style.position = "fixed";
5830
+ textarea.style.opacity = "0";
5831
+ doc.body.appendChild(textarea);
5832
+ textarea.select();
5833
+ const ok = doc.execCommand("copy");
5834
+ doc.body.removeChild(textarea);
5835
+ return ok;
5836
+ } catch {
5837
+ return false;
5838
+ }
5839
+ }
5840
+ };
5841
+ var toBase64Url = (bytes) => {
5842
+ let bin = "";
5843
+ for (let i = 0; i < bytes.length; i++) {
5844
+ bin += String.fromCharCode(bytes[i]);
5845
+ }
5846
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5847
+ };
5848
+ var HIRAGANA_START = 12353;
5849
+ var HIRAGANA_END = 12447;
5850
+ var KATAKANA_START = 12449;
5851
+ var KATAKANA_END = 12543;
5852
+ var PROLONGED_MARK = 12540;
5853
+ var SHIFT_KATAKANA = 255;
5854
+ var VALUE_PROLONGED = 223;
5855
+ var customEncode = (str) => {
5856
+ const bytes = [];
5857
+ for (let i = 0; i < str.length; i++) {
5858
+ const code = str.charCodeAt(i);
5859
+ if (code <= 127) {
5860
+ bytes.push(code);
5861
+ } else if (code === PROLONGED_MARK) {
5862
+ bytes.push(VALUE_PROLONGED);
5863
+ } else if (code >= HIRAGANA_START && code <= HIRAGANA_END) {
5864
+ bytes.push(128 + (code - HIRAGANA_START));
5865
+ } else if (code >= KATAKANA_START && code <= KATAKANA_END) {
5866
+ bytes.push(SHIFT_KATAKANA);
5867
+ bytes.push(128 + (code - 96 - HIRAGANA_START));
5868
+ }
5869
+ }
5870
+ return new Uint8Array(bytes);
5871
+ };
5872
+ var encodeMml = async (mml) => {
5873
+ try {
5874
+ if (typeof CompressionStream !== "undefined") {
5875
+ const cs = new CompressionStream("gzip");
5876
+ const w = cs.writable.getWriter();
5877
+ w.write(customEncode(mml));
5878
+ w.close();
5879
+ const buf = await new Response(cs.readable).arrayBuffer();
5880
+ return `z.${toBase64Url(new Uint8Array(buf))}`;
5881
+ }
5882
+ } catch (e) {
5883
+ console.warn(
5884
+ "[dtm] CompressionStream failed, fallback to encodeURIComponent",
5885
+ e
5886
+ );
5887
+ }
5888
+ return `u.${encodeURIComponent(mml)}`;
5889
+ };
5600
5890
  var mountMmlPlayer = (target, mml, options = {}) => {
5601
5891
  injectStyles(target.ownerDocument ?? document);
5602
5892
  const {
@@ -5742,6 +6032,83 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5742
6032
  emojiEls.push(em);
5743
6033
  emojiByTrack.set(index, em);
5744
6034
  }
6035
+ const menuContainer = doc.createElement("div");
6036
+ menuContainer.className = "dtm-player-more-container";
6037
+ const moreBtn = doc.createElement("button");
6038
+ moreBtn.type = "button";
6039
+ moreBtn.className = "dtm-player-more-btn";
6040
+ moreBtn.innerHTML = icon("more", 14);
6041
+ moreBtn.title = "\u30E1\u30CB\u30E5\u30FC";
6042
+ menuContainer.appendChild(moreBtn);
6043
+ const menuDropdown = doc.createElement("div");
6044
+ menuDropdown.className = "dtm-player-menu";
6045
+ menuDropdown.style.display = "none";
6046
+ const copyMmlItem = doc.createElement("button");
6047
+ copyMmlItem.type = "button";
6048
+ copyMmlItem.className = "dtm-player-menu-item";
6049
+ copyMmlItem.textContent = "MML\u30B3\u30D4\u30FC";
6050
+ const embedItem = doc.createElement("button");
6051
+ embedItem.type = "button";
6052
+ embedItem.className = "dtm-player-menu-item";
6053
+ embedItem.textContent = "\u57CB\u3081\u8FBC\u3080";
6054
+ menuDropdown.appendChild(copyMmlItem);
6055
+ menuDropdown.appendChild(embedItem);
6056
+ menuContainer.appendChild(menuDropdown);
6057
+ mmlHeader.appendChild(menuContainer);
6058
+ const toggleMenu = (show) => {
6059
+ const visible = show !== void 0 ? show : menuDropdown.style.display === "none";
6060
+ menuDropdown.style.display = visible ? "flex" : "none";
6061
+ if (visible) {
6062
+ moreBtn.classList.add("is-active");
6063
+ doc.addEventListener("click", handleOutsideClick);
6064
+ } else {
6065
+ moreBtn.classList.remove("is-active");
6066
+ doc.removeEventListener("click", handleOutsideClick);
6067
+ }
6068
+ };
6069
+ const handleOutsideClick = (e) => {
6070
+ if (!menuContainer.contains(e.target)) {
6071
+ toggleMenu(false);
6072
+ }
6073
+ };
6074
+ moreBtn.addEventListener("click", (e) => {
6075
+ e.stopPropagation();
6076
+ toggleMenu();
6077
+ });
6078
+ copyMmlItem.addEventListener("click", async (e) => {
6079
+ e.stopPropagation();
6080
+ const success = await copyToClipboard(doc, mml);
6081
+ if (success) {
6082
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F\uFF01";
6083
+ } else {
6084
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u5931\u6557";
6085
+ }
6086
+ setTimeout(() => {
6087
+ copyMmlItem.textContent = "MML\u30B3\u30D4\u30FC";
6088
+ }, 2e3);
6089
+ });
6090
+ embedItem.addEventListener("click", async (e) => {
6091
+ e.stopPropagation();
6092
+ embedItem.textContent = "\u751F\u6210\u4E2D...";
6093
+ try {
6094
+ const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6095
+ const payload = await encodeMml(mml);
6096
+ const url2 = `${embedBase}#${payload}`;
6097
+ const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6098
+ const success = await copyToClipboard(doc, snippet);
6099
+ if (success) {
6100
+ embedItem.textContent = "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F\uFF01";
6101
+ } else {
6102
+ embedItem.textContent = "\u30B3\u30D4\u30FC\u5931\u6557";
6103
+ }
6104
+ } catch (err2) {
6105
+ console.error("[dtm] failed to generate embed snippet", err2);
6106
+ embedItem.textContent = "\u751F\u6210\u5931\u6557";
6107
+ }
6108
+ setTimeout(() => {
6109
+ embedItem.textContent = "\u57CB\u3081\u8FBC\u3080";
6110
+ }, 2e3);
6111
+ });
5745
6112
  const promotedToImage = /* @__PURE__ */ new Set();
5746
6113
  for (const [index, lt] of lyricTracks) {
5747
6114
  const em = emojiByTrack.get(index);
@@ -6050,7 +6417,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6050
6417
  onTick: (step) => {
6051
6418
  renderPlayhead(step);
6052
6419
  },
6053
- onEnd: () => finish(),
6420
+ onEnd: (interrupted) => finish(),
6054
6421
  stepsPerBar: STEPS_PER_BAR
6055
6422
  });
6056
6423
  let playing = false;
@@ -6145,6 +6512,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6145
6512
  else play();
6146
6513
  });
6147
6514
  const destroy = () => {
6515
+ doc.removeEventListener("click", handleOutsideClick);
6148
6516
  seq.stop();
6149
6517
  peekVoices()?.stopStream();
6150
6518
  if (activePlayer === instance) activePlayer = null;
@@ -7063,6 +7431,10 @@ var mountDAW = (target, options = {}) => {
7063
7431
  0,
7064
7432
  Math.floor(step / snapGridSteps) * snapGridSteps
7065
7433
  );
7434
+ if (playbackState === "paused") {
7435
+ playbackState = "stopped";
7436
+ updateTransport();
7437
+ }
7066
7438
  redrawAll();
7067
7439
  });
7068
7440
  setDrawOffset(currentOffsetX, currentOffsetY);
@@ -7138,9 +7510,14 @@ var mountDAW = (target, options = {}) => {
7138
7510
  }
7139
7511
  redrawAll();
7140
7512
  },
7141
- onEnd: () => {
7142
- playbackState = "stopped";
7143
- currentPlayStep = 0;
7513
+ onEnd: (interrupted) => {
7514
+ if (interrupted) {
7515
+ playbackState = "paused";
7516
+ pausedPlayStep = currentPlayStep;
7517
+ } else {
7518
+ playbackState = "stopped";
7519
+ currentPlayStep = 0;
7520
+ }
7144
7521
  updateTransport();
7145
7522
  redrawAll();
7146
7523
  },
@@ -7230,13 +7607,13 @@ var mountDAW = (target, options = {}) => {
7230
7607
  redrawAll();
7231
7608
  };
7232
7609
  const togglePlay = () => {
7233
- if (playbackState === "playing") stop();
7610
+ if (playbackState === "playing") pause();
7234
7611
  else play();
7235
7612
  };
7236
7613
  const updateTransport = () => {
7237
7614
  const playing = playbackState === "playing";
7238
- const label = playing ? "\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7239
- refs.playBtn.innerHTML = `${icon(playing ? "stop" : "play")}<span>${label}</span>`;
7615
+ const label = playing ? "\u4E00\u6642\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7616
+ refs.playBtn.innerHTML = `${icon(playing ? "pause" : "play")}<span>${label}</span>`;
7240
7617
  refs.playBtn.classList.toggle("dtm-play--stop", playing);
7241
7618
  };
7242
7619
  const updateUndoRedo = () => {
@@ -7502,7 +7879,8 @@ var mountDAW = (target, options = {}) => {
7502
7879
  const metaLine = formatMmlMeta({
7503
7880
  instrument: currentInstrument || void 0,
7504
7881
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
7505
- volume: masterVolume
7882
+ volume: masterVolume,
7883
+ mode
7506
7884
  });
7507
7885
  if (refs.decomposeChordToggle.checked) {
7508
7886
  const ignoreHeavy = refs.ignoreChordHeavyToggle.checked;
@@ -7624,20 +8002,25 @@ var mountDAW = (target, options = {}) => {
7624
8002
  };
7625
8003
  const loadMML = (mml) => {
7626
8004
  if (!mml) return;
8005
+ stop();
7627
8006
  clearAll();
7628
8007
  for (const t of trackStates) t.core.setLoadMode(true);
7629
8008
  const {
7630
8009
  placements,
7631
8010
  bpm: parsedBpm,
7632
8011
  lyrics,
7633
- meta
8012
+ meta,
8013
+ mergedTrackCount
7634
8014
  } = parseMML(mml, {
7635
8015
  stepsPerBar: renderConfig.stepsPerBar,
7636
8016
  collectLyrics: true,
7637
8017
  // このDAWのトラック数を超えるチャンネルはベースへ畳み込む(従来挙動)
7638
8018
  clampTrackCount: trackStates.length
7639
8019
  });
7640
- currentInstrument = meta.instrument ?? "";
8020
+ if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
8021
+ currentInstrument = meta.instrument;
8022
+ options.onInstrumentChange?.(meta.instrument);
8023
+ }
7641
8024
  if (meta.drum && drumPatterns[meta.drum]) {
7642
8025
  currentDrumPattern = meta.drum;
7643
8026
  refs.drumSelect.value = meta.drum;
@@ -7689,6 +8072,13 @@ var mountDAW = (target, options = {}) => {
7689
8072
  redrawAll();
7690
8073
  updateTrackPanel();
7691
8074
  updateUndoRedo();
8075
+ if (!isAdvanced && mergedTrackCount > 0) {
8076
+ 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";
8077
+ refs.mmlLoadNote.classList.remove("dtm-hidden");
8078
+ } else {
8079
+ refs.mmlLoadNote.textContent = "";
8080
+ refs.mmlLoadNote.classList.add("dtm-hidden");
8081
+ }
7692
8082
  };
7693
8083
  const applyChord = () => {
7694
8084
  const active = getActive();
@@ -7721,6 +8111,7 @@ var mountDAW = (target, options = {}) => {
7721
8111
  applyMidiSelection(midi, selected);
7722
8112
  };
7723
8113
  const applyMidiSelection = (midi, selectedIndices) => {
8114
+ stop();
7724
8115
  clearAll();
7725
8116
  for (const t of trackStates) t.core.setLoadMode(true);
7726
8117
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -8152,6 +8543,25 @@ var mountDAW = (target, options = {}) => {
8152
8543
  const setLoading = (loading) => {
8153
8544
  refs.topbar.classList.toggle("is-loading", loading);
8154
8545
  };
8546
+ const getCurrentPlayStep = () => {
8547
+ if (playbackState === "playing") return currentPlayStep;
8548
+ if (playbackState === "paused") return pausedPlayStep;
8549
+ return playStartStep;
8550
+ };
8551
+ const forcePauseAt = (step) => {
8552
+ playStartStep = step;
8553
+ pausedPlayStep = step;
8554
+ currentPlayStep = step;
8555
+ playbackState = "paused";
8556
+ const canvas = getGridCanvas();
8557
+ currentOffsetX = Math.max(
8558
+ 0,
8559
+ step * renderConfig.stepWidth - canvas.width * 0.5
8560
+ );
8561
+ setDrawOffset(currentOffsetX, currentOffsetY);
8562
+ updateTransport();
8563
+ redrawAll();
8564
+ };
8155
8565
  return {
8156
8566
  play,
8157
8567
  pause,
@@ -8189,9 +8599,12 @@ var mountDAW = (target, options = {}) => {
8189
8599
  exportMIDI: exportMIDI2,
8190
8600
  setBpm,
8191
8601
  getPlaybackState: () => playbackState,
8602
+ getCurrentPlayStep,
8603
+ forcePauseAt,
8192
8604
  setLoading,
8193
8605
  destroy: () => {
8194
8606
  sequencer.stop();
8607
+ options.singingVoices?.stopStream();
8195
8608
  resizeObserver.disconnect();
8196
8609
  document.removeEventListener("pointermove", onPointerMove);
8197
8610
  document.removeEventListener("pointerup", onPointerUp);
@@ -8201,110 +8614,6 @@ var mountDAW = (target, options = {}) => {
8201
8614
  };
8202
8615
  };
8203
8616
 
8204
- // src/instrument-presets.ts
8205
- var INSTRUMENT_PRESETS = {
8206
- // --- STANDARD: 汎用性と完成度重視 ---
8207
- piano: {
8208
- displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
8209
- 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",
8210
- melody: "Acoustic Grand Piano",
8211
- submelody: "Vibraphone",
8212
- bass: "Electric Bass (finger)",
8213
- chord: "Pad 2 (warm)"
8214
- },
8215
- acoustic: {
8216
- displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
8217
- description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
8218
- melody: "Acoustic Guitar (steel)",
8219
- submelody: "Harmonica",
8220
- bass: "Acoustic Bass",
8221
- chord: "Acoustic Guitar (nylon)"
8222
- },
8223
- jazz_night: {
8224
- displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
8225
- 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",
8226
- melody: "Electric Piano 1",
8227
- submelody: "Flute",
8228
- bass: "Acoustic Bass",
8229
- chord: "Electric Guitar (jazz)"
8230
- },
8231
- // --- MODERN & VIBE: エッジの効いた現代的な響き ---
8232
- synth_pop: {
8233
- displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
8234
- description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
8235
- melody: "Lead 2 (sawtooth)",
8236
- submelody: "Lead 4 (chiff)",
8237
- bass: "Synth Bass 2",
8238
- chord: "Pad 3 (polysynth)"
8239
- },
8240
- cyber_punk: {
8241
- displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
8242
- 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",
8243
- melody: "Lead 8 (bass + lead)",
8244
- submelody: "Lead 5 (charang)",
8245
- bass: "Synth Bass 2",
8246
- chord: "Pad 8 (sweep)"
8247
- },
8248
- rock: {
8249
- displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
8250
- description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
8251
- melody: "Distortion Guitar",
8252
- submelody: "Rock Organ",
8253
- bass: "Electric Bass (pick)",
8254
- chord: "Overdriven Guitar"
8255
- },
8256
- // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
8257
- orchestra: {
8258
- displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
8259
- description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
8260
- melody: "French Horn",
8261
- submelody: "Pizzicato Strings",
8262
- bass: "Cello",
8263
- chord: "Tremolo Strings"
8264
- },
8265
- japanese_wa: {
8266
- displayName: "\u548C\u98A8\u30FB\u96C5",
8267
- description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
8268
- melody: "Koto",
8269
- submelody: "Shamisen",
8270
- bass: "Taiko Drum",
8271
- chord: "Shakuhachi"
8272
- },
8273
- arabic_exotic: {
8274
- displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
8275
- 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",
8276
- melody: "Sitar",
8277
- submelody: "Bagpipe",
8278
- bass: "Fretless Bass",
8279
- chord: "Kalimba"
8280
- },
8281
- // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
8282
- fantasy_rpg: {
8283
- displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
8284
- description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
8285
- melody: "Ocarina",
8286
- submelody: "Celesta",
8287
- bass: "Timpani",
8288
- chord: "Orchestral Harp"
8289
- },
8290
- ambient_cloud: {
8291
- displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
8292
- description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
8293
- melody: "Lead 6 (voice)",
8294
- submelody: "Music Box",
8295
- bass: "Synth Bass 1",
8296
- chord: "Pad 7 (halo)"
8297
- },
8298
- retro_game: {
8299
- displayName: "8-bit \u30EC\u30C8\u30ED",
8300
- 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",
8301
- melody: "Lead 1 (square)",
8302
- submelody: "Lead 2 (sawtooth)",
8303
- bass: "Synth Bass 1",
8304
- chord: "Clavinet"
8305
- }
8306
- };
8307
-
8308
8617
  // src/headless-player.ts
8309
8618
  var STEPS_PER_BAR2 = 192;
8310
8619
  var playMML = (mml, options = {}) => {
@@ -8355,7 +8664,7 @@ var playMML = (mml, options = {}) => {
8355
8664
  },
8356
8665
  onTick: () => {
8357
8666
  },
8358
- onEnd: () => finish(),
8667
+ onEnd: (interrupted) => finish(),
8359
8668
  stepsPerBar: STEPS_PER_BAR2
8360
8669
  });
8361
8670
  const finish = () => {
@@ -9047,32 +9356,56 @@ var createDtmStudio = async (options = {}) => {
9047
9356
  return p;
9048
9357
  };
9049
9358
  const defaultPreset = options.defaultPreset ?? "retro_game";
9359
+ const getRoleForTrackIndex = (idx, mode = "simple") => {
9360
+ if (mode === "simple") {
9361
+ if (idx === 0) return "melody";
9362
+ if (idx === 1) return "submelody";
9363
+ if (idx === 2) return "bass";
9364
+ return "chord";
9365
+ } else {
9366
+ return TRACK_ROLES[idx] ?? `t${idx}`;
9367
+ }
9368
+ };
9369
+ const getRoleFromTrackId = (trackId, mode = "simple") => {
9370
+ if (trackId === "melody" || trackId === "submelody" || trackId === "bass" || trackId === "chord") {
9371
+ return trackId;
9372
+ }
9373
+ if (trackId.startsWith("t")) {
9374
+ const idx = Number(trackId.substring(1));
9375
+ if (!isNaN(idx)) {
9376
+ return getRoleForTrackIndex(idx, mode);
9377
+ }
9378
+ }
9379
+ return trackId;
9380
+ };
9050
9381
  const instrumentNameFor = (preset, trackId) => preset[trackId] ?? preset.melody;
9051
- const resolveSoundFont = (presetKey, trackId) => {
9382
+ const resolveSoundFont = (presetKey, trackId, mode = "simple") => {
9052
9383
  const preset = INSTRUMENT_PRESETS[presetKey];
9053
9384
  if (!preset) return void 0;
9054
- const key = nameToKey[instrumentNameFor(preset, trackId)];
9385
+ const role = getRoleFromTrackId(trackId, mode);
9386
+ const key = nameToKey[instrumentNameFor(preset, role)];
9055
9387
  return key ? soundFonts.get(key) : void 0;
9056
9388
  };
9057
- const loadPreset = async (presetKey, trackIds = [...TRACK_ROLES]) => {
9389
+ const loadPreset = async (presetKey, trackIds = [...TRACK_ROLES], mode = "simple") => {
9058
9390
  const preset = INSTRUMENT_PRESETS[presetKey];
9059
9391
  if (!preset) return;
9060
9392
  await listReady;
9061
9393
  const keys = /* @__PURE__ */ new Set();
9062
9394
  for (const trackId of trackIds) {
9063
- const key = nameToKey[instrumentNameFor(preset, trackId)];
9395
+ const role = getRoleFromTrackId(trackId, mode);
9396
+ const key = nameToKey[instrumentNameFor(preset, role)];
9064
9397
  if (key) keys.add(key);
9065
9398
  }
9066
9399
  await Promise.all([...keys].map((key) => loadInstrument(key)));
9067
9400
  };
9068
- const applyPreset = async (daw, presetKey, trackIds, loadingTarget) => {
9401
+ const applyPreset = async (daw, presetKey, trackIds, loadingTarget, mode = "simple") => {
9069
9402
  const wasPlaying = daw.getPlaybackState() === "playing";
9070
9403
  if (wasPlaying) daw.pause();
9071
9404
  const overlay = loadingTarget ? showLoadingOverlay(loadingTarget) : null;
9072
9405
  daw.setLoading?.(true);
9073
9406
  try {
9074
9407
  daw.setInstrument(presetKey);
9075
- await loadPreset(presetKey, trackIds);
9408
+ await loadPreset(presetKey, trackIds, mode);
9076
9409
  } finally {
9077
9410
  overlay?.remove();
9078
9411
  daw.setLoading?.(false);
@@ -9107,8 +9440,10 @@ var createDtmStudio = async (options = {}) => {
9107
9440
  const key = select.value;
9108
9441
  opts.onChange?.(key);
9109
9442
  const trackIds = opts.getTrackIds?.() ?? [...TRACK_ROLES];
9443
+ const isAdvanced = trackIds.includes("t0");
9444
+ const mode = isAdvanced ? "advanced" : "simple";
9110
9445
  try {
9111
- await applyPreset(daw, key, trackIds, opts.loadingTarget);
9446
+ await applyPreset(daw, key, trackIds, opts.loadingTarget, mode);
9112
9447
  } finally {
9113
9448
  busy = false;
9114
9449
  }
@@ -9149,13 +9484,20 @@ var createDtmStudio = async (options = {}) => {
9149
9484
  const mountedPlayers = [];
9150
9485
  const mountedModeSwitches = [];
9151
9486
  const mountEditor = (target, opts = {}) => {
9152
- const { preset, presetUI, ...dawOverrides } = opts;
9487
+ const { preset, presetUI, onInstrumentChange, ...dawOverrides } = opts;
9153
9488
  const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
9154
9489
  const trackIds = tracks.map((t) => t.id);
9155
9490
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
9156
- let editorPreset = presetKey;
9491
+ const meta = opts.initialMML ? parseMmlMeta(opts.initialMML) : {};
9492
+ const initialPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : presetKey;
9493
+ let editorPreset = initialPreset;
9494
+ const isAdvancedMode = dawOverrides.mode === "advanced";
9157
9495
  const playNote = (e) => {
9158
- const sf = resolveSoundFont(editorPreset, e.trackId);
9496
+ const sf = resolveSoundFont(
9497
+ editorPreset,
9498
+ e.trackId,
9499
+ isAdvancedMode ? "advanced" : "simple"
9500
+ );
9159
9501
  if (!sf) return;
9160
9502
  sf.play({
9161
9503
  ctx: audioCtx,
@@ -9166,6 +9508,14 @@ var createDtmStudio = async (options = {}) => {
9166
9508
  duration: e.duration
9167
9509
  });
9168
9510
  };
9511
+ let presetSelect = null;
9512
+ const handleInstrumentChange = (key) => {
9513
+ editorPreset = key;
9514
+ if (presetSelect) {
9515
+ presetSelect.setValue(key);
9516
+ }
9517
+ onInstrumentChange?.(key);
9518
+ };
9169
9519
  const base = {
9170
9520
  getAudioTime: () => audioCtx.currentTime,
9171
9521
  onResumeAudio: resumeAudio,
@@ -9174,19 +9524,19 @@ var createDtmStudio = async (options = {}) => {
9174
9524
  singingVoices,
9175
9525
  parseMidi,
9176
9526
  onToggleRecord,
9527
+ onInstrumentChange: handleInstrumentChange,
9177
9528
  ...dawOverrides
9178
9529
  };
9179
9530
  const daw = mountDAW(target, base);
9180
9531
  mountedEditors.push(daw);
9181
9532
  const wantPresetUI = presetUI ?? features.presetUI;
9182
- let presetSelect = null;
9183
9533
  if (wantPresetUI) {
9184
9534
  editorPresetSelects.get(target)?.destroy();
9185
9535
  const rollEl = target.querySelector('[data-dtm="roll"]');
9186
9536
  presetSelect = mountPresetSelect(target, {
9187
9537
  getDaw: () => daw,
9188
9538
  getTrackIds: () => trackIds,
9189
- value: presetKey,
9539
+ value: initialPreset,
9190
9540
  loadingTarget: rollEl ?? target,
9191
9541
  position: "prepend",
9192
9542
  // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
@@ -9196,9 +9546,13 @@ var createDtmStudio = async (options = {}) => {
9196
9546
  });
9197
9547
  editorPresetSelects.set(target, presetSelect);
9198
9548
  }
9199
- daw.setInstrument(presetKey);
9549
+ daw.setInstrument(initialPreset);
9200
9550
  daw.setLoading?.(true);
9201
- void loadPreset(presetKey, trackIds).finally(() => {
9551
+ void loadPreset(
9552
+ initialPreset,
9553
+ trackIds,
9554
+ isAdvancedMode ? "advanced" : "simple"
9555
+ ).finally(() => {
9202
9556
  daw.setLoading?.(false);
9203
9557
  });
9204
9558
  const destroy = () => {
@@ -9209,7 +9563,17 @@ var createDtmStudio = async (options = {}) => {
9209
9563
  const i = mountedEditors.indexOf(daw);
9210
9564
  if (i >= 0) mountedEditors.splice(i, 1);
9211
9565
  };
9212
- return { ...daw, destroy };
9566
+ return {
9567
+ ...daw,
9568
+ setInstrument: (name) => {
9569
+ daw.setInstrument(name);
9570
+ editorPreset = name;
9571
+ if (presetSelect) {
9572
+ presetSelect.setValue(name);
9573
+ }
9574
+ },
9575
+ destroy
9576
+ };
9213
9577
  };
9214
9578
  const mountModeSwitch = (target, opts) => {
9215
9579
  const doc = target.ownerDocument;
@@ -9299,15 +9663,30 @@ var createDtmStudio = async (options = {}) => {
9299
9663
  const parsed = parseMML(mml, {});
9300
9664
  const meta = parsed.meta ?? {};
9301
9665
  const playerPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : defaultPreset;
9666
+ const isAdvancedMode = meta.mode === "advanced";
9302
9667
  const trackIndices = [
9303
9668
  ...new Set(parsed.placements.map((p) => p.trackIndex))
9304
9669
  ];
9305
- const trackIds = trackIndices.map((idx) => TRACK_ROLES[idx] ?? `t${idx}`);
9670
+ const trackIds = trackIndices.map(
9671
+ (idx) => getRoleForTrackIndex(idx, isAdvancedMode ? "advanced" : "simple")
9672
+ );
9306
9673
  const loadTrackIds = trackIds.length > 0 ? trackIds : [...TRACK_ROLES];
9307
- void loadPreset(playerPreset, loadTrackIds);
9674
+ void loadPreset(
9675
+ playerPreset,
9676
+ loadTrackIds,
9677
+ isAdvancedMode ? "advanced" : "simple"
9678
+ );
9308
9679
  const playPlayerNote = (e) => {
9309
- const role = TRACK_ROLES[Number(e.trackId)] ?? `t${e.trackId}`;
9310
- const sf = resolveSoundFont(playerPreset, role);
9680
+ const idx = Number(e.trackId);
9681
+ const role = getRoleForTrackIndex(
9682
+ idx,
9683
+ isAdvancedMode ? "advanced" : "simple"
9684
+ );
9685
+ const sf = resolveSoundFont(
9686
+ playerPreset,
9687
+ role,
9688
+ isAdvancedMode ? "advanced" : "simple"
9689
+ );
9311
9690
  if (!sf) return;
9312
9691
  sf.play({
9313
9692
  ctx: audioCtx,