@onjmin/dtm 0.1.23 → 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) {
@@ -4069,6 +4178,7 @@ var parseMML = (mml, options = {}) => {
4069
4178
  bpm,
4070
4179
  tokenTracks: collectTokens ? tokenTracks : void 0,
4071
4180
  lyrics: collectLyrics ? /* @__PURE__ */ new Map() : void 0,
4181
+ mergedTrackCount: 0,
4072
4182
  meta: {}
4073
4183
  };
4074
4184
  }
@@ -4081,13 +4191,24 @@ var parseMML = (mml, options = {}) => {
4081
4191
  const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
4082
4192
  const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
4083
4193
  let trackIndex = 0;
4194
+ let sourceTrackIndex = 0;
4084
4195
  let octave = 4;
4085
4196
  let currentStep = 0;
4086
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
+ };
4087
4207
  for (const rawPart of parts) {
4088
4208
  const part = rawPart.trim();
4089
4209
  if (part.startsWith("@")) {
4090
4210
  let idx = Number.parseInt(part.substring(1), 10);
4211
+ sourceTrackIndex = idx;
4091
4212
  if (clampTrackCount !== void 0 && idx >= clampTrackCount)
4092
4213
  idx = clampTrackCount - 1;
4093
4214
  trackIndex = idx;
@@ -4211,6 +4332,7 @@ var parseMML = (mml, options = {}) => {
4211
4332
  }
4212
4333
  if (j < body.length && body[j] === "]") j++;
4213
4334
  const steps = parseLength();
4335
+ if (chordNotes.length > 0) recordContributor();
4214
4336
  for (const p of chordNotes) {
4215
4337
  placements.push({
4216
4338
  trackIndex,
@@ -4234,6 +4356,7 @@ var parseMML = (mml, options = {}) => {
4234
4356
  }
4235
4357
  const midiPitch = (octave + 1) * 12 + pitch;
4236
4358
  const steps = parseLength();
4359
+ recordContributor();
4237
4360
  placements.push({
4238
4361
  trackIndex,
4239
4362
  startStep: currentStep,
@@ -4247,11 +4370,16 @@ var parseMML = (mml, options = {}) => {
4247
4370
  }
4248
4371
  }
4249
4372
  }
4373
+ let mergedTrackCount = 0;
4374
+ for (const set of contributors.values()) {
4375
+ if (set.size >= 2) mergedTrackCount++;
4376
+ }
4250
4377
  return {
4251
4378
  placements,
4252
4379
  bpm,
4253
4380
  tokenTracks: collectTokens ? tokenTracks : void 0,
4254
4381
  lyrics,
4382
+ mergedTrackCount,
4255
4383
  meta
4256
4384
  };
4257
4385
  };
@@ -4313,6 +4441,8 @@ var createSequencer = (options) => {
4313
4441
  let active = false;
4314
4442
  let fromStepValue = 0;
4315
4443
  let trackVolumeMap = /* @__PURE__ */ new Map();
4444
+ let lastRealTime = -1;
4445
+ let lastAudioTime = -1;
4316
4446
  let isLooping = false;
4317
4447
  let loopStartStep = 0;
4318
4448
  let loopEndStep = 0;
@@ -4386,6 +4516,21 @@ var createSequencer = (options) => {
4386
4516
  const sps = secondsPerStep();
4387
4517
  const time = options.getAudioTime() - startTime;
4388
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;
4389
4534
  for (const track of options.getTracks()) {
4390
4535
  trackVolumeMap.set(track.id, track.volume);
4391
4536
  }
@@ -4462,7 +4607,7 @@ var createSequencer = (options) => {
4462
4607
  const lastDuration = last?.duration ?? 0;
4463
4608
  if (nowIndex >= timeline.length && time > lastWhen + lastDuration + 0.1) {
4464
4609
  stop();
4465
- options.onEnd();
4610
+ options.onEnd(false);
4466
4611
  }
4467
4612
  }
4468
4613
  };
@@ -4503,6 +4648,8 @@ var createSequencer = (options) => {
4503
4648
  }
4504
4649
  loopBase = 0;
4505
4650
  lastPlayStep = fromStepValue - 1e-4;
4651
+ lastRealTime = -1;
4652
+ lastAudioTime = -1;
4506
4653
  intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
4507
4654
  animationId = requestAnimationFrame(animate);
4508
4655
  };
@@ -4700,11 +4847,11 @@ var DAW_CSS = `
4700
4847
  }
4701
4848
  .dtm-btn:active { transform: translate(3px,3px); box-shadow: none; }
4702
4849
  .dtm-btn:disabled { opacity: .3; cursor: default; box-shadow: none; }
4850
+ .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4703
4851
  .dtm-btn--primary { border-color: var(--dtm-primary); background: var(--dtm-primary); color: var(--dtm-pfg); }
4704
4852
  .dtm-btn--success { border-color: var(--dtm-success); background: var(--dtm-success); color: var(--c-black); }
4705
4853
  .dtm-btn--danger { border-color: var(--dtm-danger); background: var(--dtm-danger); color: var(--c-white); }
4706
4854
  .dtm-btn--accent { border-color: var(--dtm-accent); background: var(--dtm-accent); color: var(--c-black); }
4707
- .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4708
4855
  .dtm-btn--icon { padding: 0; }
4709
4856
 
4710
4857
  /* \u2500\u2500\u2500 \u30A2\u30A4\u30B3\u30F3\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
@@ -5338,6 +5485,18 @@ var DAW_CSS = `
5338
5485
  }
5339
5486
 
5340
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 "; }
5341
5500
  .dtm-grow { flex: 1 1 auto; }
5342
5501
  .dtm-lyric-icon {
5343
5502
  flex: 0 0 auto;
@@ -5437,6 +5596,62 @@ var DAW_CSS = `
5437
5596
  white-space: nowrap;
5438
5597
  }
5439
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
+ }
5440
5655
  .dtm-player-emoji {
5441
5656
  position: relative;
5442
5657
  display: inline-flex;
@@ -5712,6 +5927,75 @@ var showBalloon = (balloonEl) => {
5712
5927
  hideActiveBalloon();
5713
5928
  }, 3e3);
5714
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
+ };
5715
5999
  var mountMmlPlayer = (target, mml, options = {}) => {
5716
6000
  injectStyles(target.ownerDocument ?? document);
5717
6001
  const {
@@ -5857,6 +6141,83 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5857
6141
  emojiEls.push(em);
5858
6142
  emojiByTrack.set(index, em);
5859
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
+ });
5860
6221
  const promotedToImage = /* @__PURE__ */ new Set();
5861
6222
  for (const [index, lt] of lyricTracks) {
5862
6223
  const em = emojiByTrack.get(index);
@@ -6165,7 +6526,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6165
6526
  onTick: (step) => {
6166
6527
  renderPlayhead(step);
6167
6528
  },
6168
- onEnd: () => finish(),
6529
+ onEnd: (interrupted) => finish(),
6169
6530
  stepsPerBar: STEPS_PER_BAR
6170
6531
  });
6171
6532
  let playing = false;
@@ -6260,6 +6621,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6260
6621
  else play();
6261
6622
  });
6262
6623
  const destroy = () => {
6624
+ doc.removeEventListener("click", handleOutsideClick);
6263
6625
  seq.stop();
6264
6626
  peekVoices()?.stopStream();
6265
6627
  if (activePlayer === instance) activePlayer = null;
@@ -7178,6 +7540,10 @@ var mountDAW = (target, options = {}) => {
7178
7540
  0,
7179
7541
  Math.floor(step / snapGridSteps) * snapGridSteps
7180
7542
  );
7543
+ if (playbackState === "paused") {
7544
+ playbackState = "stopped";
7545
+ updateTransport();
7546
+ }
7181
7547
  redrawAll();
7182
7548
  });
7183
7549
  setDrawOffset(currentOffsetX, currentOffsetY);
@@ -7253,9 +7619,14 @@ var mountDAW = (target, options = {}) => {
7253
7619
  }
7254
7620
  redrawAll();
7255
7621
  },
7256
- onEnd: () => {
7257
- playbackState = "stopped";
7258
- currentPlayStep = 0;
7622
+ onEnd: (interrupted) => {
7623
+ if (interrupted) {
7624
+ playbackState = "paused";
7625
+ pausedPlayStep = currentPlayStep;
7626
+ } else {
7627
+ playbackState = "stopped";
7628
+ currentPlayStep = 0;
7629
+ }
7259
7630
  updateTransport();
7260
7631
  redrawAll();
7261
7632
  },
@@ -7345,13 +7716,13 @@ var mountDAW = (target, options = {}) => {
7345
7716
  redrawAll();
7346
7717
  };
7347
7718
  const togglePlay = () => {
7348
- if (playbackState === "playing") stop();
7719
+ if (playbackState === "playing") pause();
7349
7720
  else play();
7350
7721
  };
7351
7722
  const updateTransport = () => {
7352
7723
  const playing = playbackState === "playing";
7353
- const label = playing ? "\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7354
- 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>`;
7355
7726
  refs.playBtn.classList.toggle("dtm-play--stop", playing);
7356
7727
  };
7357
7728
  const updateUndoRedo = () => {
@@ -7740,20 +8111,25 @@ var mountDAW = (target, options = {}) => {
7740
8111
  };
7741
8112
  const loadMML = (mml) => {
7742
8113
  if (!mml) return;
8114
+ stop();
7743
8115
  clearAll();
7744
8116
  for (const t of trackStates) t.core.setLoadMode(true);
7745
8117
  const {
7746
8118
  placements,
7747
8119
  bpm: parsedBpm,
7748
8120
  lyrics,
7749
- meta
8121
+ meta,
8122
+ mergedTrackCount
7750
8123
  } = parseMML(mml, {
7751
8124
  stepsPerBar: renderConfig.stepsPerBar,
7752
8125
  collectLyrics: true,
7753
8126
  // このDAWのトラック数を超えるチャンネルはベースへ畳み込む(従来挙動)
7754
8127
  clampTrackCount: trackStates.length
7755
8128
  });
7756
- currentInstrument = meta.instrument ?? "";
8129
+ if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
8130
+ currentInstrument = meta.instrument;
8131
+ options.onInstrumentChange?.(meta.instrument);
8132
+ }
7757
8133
  if (meta.drum && drumPatterns[meta.drum]) {
7758
8134
  currentDrumPattern = meta.drum;
7759
8135
  refs.drumSelect.value = meta.drum;
@@ -7805,6 +8181,13 @@ var mountDAW = (target, options = {}) => {
7805
8181
  redrawAll();
7806
8182
  updateTrackPanel();
7807
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
+ }
7808
8191
  };
7809
8192
  const applyChord = () => {
7810
8193
  const active = getActive();
@@ -7837,6 +8220,7 @@ var mountDAW = (target, options = {}) => {
7837
8220
  applyMidiSelection(midi, selected);
7838
8221
  };
7839
8222
  const applyMidiSelection = (midi, selectedIndices) => {
8223
+ stop();
7840
8224
  clearAll();
7841
8225
  for (const t of trackStates) t.core.setLoadMode(true);
7842
8226
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -8268,6 +8652,25 @@ var mountDAW = (target, options = {}) => {
8268
8652
  const setLoading = (loading) => {
8269
8653
  refs.topbar.classList.toggle("is-loading", loading);
8270
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
+ };
8271
8674
  return {
8272
8675
  play,
8273
8676
  pause,
@@ -8305,9 +8708,12 @@ var mountDAW = (target, options = {}) => {
8305
8708
  exportMIDI: exportMIDI2,
8306
8709
  setBpm,
8307
8710
  getPlaybackState: () => playbackState,
8711
+ getCurrentPlayStep,
8712
+ forcePauseAt,
8308
8713
  setLoading,
8309
8714
  destroy: () => {
8310
8715
  sequencer.stop();
8716
+ options.singingVoices?.stopStream();
8311
8717
  resizeObserver.disconnect();
8312
8718
  document.removeEventListener("pointermove", onPointerMove);
8313
8719
  document.removeEventListener("pointerup", onPointerUp);
@@ -8317,110 +8723,6 @@ var mountDAW = (target, options = {}) => {
8317
8723
  };
8318
8724
  };
8319
8725
 
8320
- // src/instrument-presets.ts
8321
- var INSTRUMENT_PRESETS = {
8322
- // --- STANDARD: 汎用性と完成度重視 ---
8323
- piano: {
8324
- displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
8325
- 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",
8326
- melody: "Acoustic Grand Piano",
8327
- submelody: "Vibraphone",
8328
- bass: "Electric Bass (finger)",
8329
- chord: "Pad 2 (warm)"
8330
- },
8331
- acoustic: {
8332
- displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
8333
- description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
8334
- melody: "Acoustic Guitar (steel)",
8335
- submelody: "Harmonica",
8336
- bass: "Acoustic Bass",
8337
- chord: "Acoustic Guitar (nylon)"
8338
- },
8339
- jazz_night: {
8340
- displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
8341
- 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",
8342
- melody: "Electric Piano 1",
8343
- submelody: "Flute",
8344
- bass: "Acoustic Bass",
8345
- chord: "Electric Guitar (jazz)"
8346
- },
8347
- // --- MODERN & VIBE: エッジの効いた現代的な響き ---
8348
- synth_pop: {
8349
- displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
8350
- description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
8351
- melody: "Lead 2 (sawtooth)",
8352
- submelody: "Lead 4 (chiff)",
8353
- bass: "Synth Bass 2",
8354
- chord: "Pad 3 (polysynth)"
8355
- },
8356
- cyber_punk: {
8357
- displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
8358
- 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",
8359
- melody: "Lead 8 (bass + lead)",
8360
- submelody: "Lead 5 (charang)",
8361
- bass: "Synth Bass 2",
8362
- chord: "Pad 8 (sweep)"
8363
- },
8364
- rock: {
8365
- displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
8366
- description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
8367
- melody: "Distortion Guitar",
8368
- submelody: "Rock Organ",
8369
- bass: "Electric Bass (pick)",
8370
- chord: "Overdriven Guitar"
8371
- },
8372
- // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
8373
- orchestra: {
8374
- displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
8375
- description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
8376
- melody: "French Horn",
8377
- submelody: "Pizzicato Strings",
8378
- bass: "Cello",
8379
- chord: "Tremolo Strings"
8380
- },
8381
- japanese_wa: {
8382
- displayName: "\u548C\u98A8\u30FB\u96C5",
8383
- description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
8384
- melody: "Koto",
8385
- submelody: "Shamisen",
8386
- bass: "Taiko Drum",
8387
- chord: "Shakuhachi"
8388
- },
8389
- arabic_exotic: {
8390
- displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
8391
- 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",
8392
- melody: "Sitar",
8393
- submelody: "Bagpipe",
8394
- bass: "Fretless Bass",
8395
- chord: "Kalimba"
8396
- },
8397
- // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
8398
- fantasy_rpg: {
8399
- displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
8400
- description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
8401
- melody: "Ocarina",
8402
- submelody: "Celesta",
8403
- bass: "Timpani",
8404
- chord: "Orchestral Harp"
8405
- },
8406
- ambient_cloud: {
8407
- displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
8408
- description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
8409
- melody: "Lead 6 (voice)",
8410
- submelody: "Music Box",
8411
- bass: "Synth Bass 1",
8412
- chord: "Pad 7 (halo)"
8413
- },
8414
- retro_game: {
8415
- displayName: "8-bit \u30EC\u30C8\u30ED",
8416
- 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",
8417
- melody: "Lead 1 (square)",
8418
- submelody: "Lead 2 (sawtooth)",
8419
- bass: "Synth Bass 1",
8420
- chord: "Clavinet"
8421
- }
8422
- };
8423
-
8424
8726
  // src/headless-player.ts
8425
8727
  var STEPS_PER_BAR2 = 192;
8426
8728
  var playMML = (mml, options = {}) => {
@@ -8471,7 +8773,7 @@ var playMML = (mml, options = {}) => {
8471
8773
  },
8472
8774
  onTick: () => {
8473
8775
  },
8474
- onEnd: () => finish(),
8776
+ onEnd: (interrupted) => finish(),
8475
8777
  stepsPerBar: STEPS_PER_BAR2
8476
8778
  });
8477
8779
  const finish = () => {
@@ -9292,11 +9594,13 @@ var createDtmStudio = async (options = {}) => {
9292
9594
  const mountedPlayers = [];
9293
9595
  const mountedModeSwitches = [];
9294
9596
  const mountEditor = (target, opts = {}) => {
9295
- const { preset, presetUI, ...dawOverrides } = opts;
9597
+ const { preset, presetUI, onInstrumentChange, ...dawOverrides } = opts;
9296
9598
  const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
9297
9599
  const trackIds = tracks.map((t) => t.id);
9298
9600
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
9299
- 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;
9300
9604
  const isAdvancedMode = dawOverrides.mode === "advanced";
9301
9605
  const playNote = (e) => {
9302
9606
  const sf = resolveSoundFont(
@@ -9314,6 +9618,14 @@ var createDtmStudio = async (options = {}) => {
9314
9618
  duration: e.duration
9315
9619
  });
9316
9620
  };
9621
+ let presetSelect = null;
9622
+ const handleInstrumentChange = (key) => {
9623
+ editorPreset = key;
9624
+ if (presetSelect) {
9625
+ presetSelect.setValue(key);
9626
+ }
9627
+ onInstrumentChange?.(key);
9628
+ };
9317
9629
  const base = {
9318
9630
  getAudioTime: () => audioCtx.currentTime,
9319
9631
  onResumeAudio: resumeAudio,
@@ -9322,19 +9634,19 @@ var createDtmStudio = async (options = {}) => {
9322
9634
  singingVoices,
9323
9635
  parseMidi,
9324
9636
  onToggleRecord,
9637
+ onInstrumentChange: handleInstrumentChange,
9325
9638
  ...dawOverrides
9326
9639
  };
9327
9640
  const daw = mountDAW(target, base);
9328
9641
  mountedEditors.push(daw);
9329
9642
  const wantPresetUI = presetUI ?? features.presetUI;
9330
- let presetSelect = null;
9331
9643
  if (wantPresetUI) {
9332
9644
  editorPresetSelects.get(target)?.destroy();
9333
9645
  const rollEl = target.querySelector('[data-dtm="roll"]');
9334
9646
  presetSelect = mountPresetSelect(target, {
9335
9647
  getDaw: () => daw,
9336
9648
  getTrackIds: () => trackIds,
9337
- value: presetKey,
9649
+ value: initialPreset,
9338
9650
  loadingTarget: rollEl ?? target,
9339
9651
  position: "prepend",
9340
9652
  // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
@@ -9344,10 +9656,10 @@ var createDtmStudio = async (options = {}) => {
9344
9656
  });
9345
9657
  editorPresetSelects.set(target, presetSelect);
9346
9658
  }
9347
- daw.setInstrument(presetKey);
9659
+ daw.setInstrument(initialPreset);
9348
9660
  daw.setLoading?.(true);
9349
9661
  void loadPreset(
9350
- presetKey,
9662
+ initialPreset,
9351
9663
  trackIds,
9352
9664
  isAdvancedMode ? "advanced" : "simple"
9353
9665
  ).finally(() => {
@@ -9361,7 +9673,17 @@ var createDtmStudio = async (options = {}) => {
9361
9673
  const i = mountedEditors.indexOf(daw);
9362
9674
  if (i >= 0) mountedEditors.splice(i, 1);
9363
9675
  };
9364
- 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
+ };
9365
9687
  };
9366
9688
  const mountModeSwitch = (target, opts) => {
9367
9689
  const doc = target.ownerDocument;