@onjmin/dtm 0.1.23 → 0.1.25

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
 
@@ -1424,6 +1428,7 @@ var buildUI = (target, options) => {
1424
1428
  <div class="dtm-modal-body" data-dtm="modal-body"></div>
1425
1429
  </div>
1426
1430
  </div>
1431
+
1427
1432
  </div>`;
1428
1433
  const root = q(target, '[data-dtm="root"]');
1429
1434
  const sel = (name) => q(root, `[data-dtm="${name}"]`);
@@ -1467,6 +1472,7 @@ var buildUI = (target, options) => {
1467
1472
  midiPanel: sel("midi-panel"),
1468
1473
  mmlInput: sel("mml-input"),
1469
1474
  mmlLoadBtn: sel("mml-load"),
1475
+ mmlLoadNote: sel("mml-load-note"),
1470
1476
  shiftSelect: sel("shift-select"),
1471
1477
  shiftApplyBtn: sel("shift-apply"),
1472
1478
  macroClear: sel("macro-clear"),
@@ -1620,6 +1626,110 @@ var DRUM_PATTERNS = {
1620
1626
  ]
1621
1627
  };
1622
1628
 
1629
+ // src/instrument-presets.ts
1630
+ var INSTRUMENT_PRESETS = {
1631
+ // --- STANDARD: 汎用性と完成度重視 ---
1632
+ piano: {
1633
+ displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
1634
+ 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",
1635
+ melody: "Acoustic Grand Piano",
1636
+ submelody: "Vibraphone",
1637
+ bass: "Electric Bass (finger)",
1638
+ chord: "Pad 2 (warm)"
1639
+ },
1640
+ acoustic: {
1641
+ displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
1642
+ description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
1643
+ melody: "Acoustic Guitar (steel)",
1644
+ submelody: "Harmonica",
1645
+ bass: "Acoustic Bass",
1646
+ chord: "Acoustic Guitar (nylon)"
1647
+ },
1648
+ jazz_night: {
1649
+ displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
1650
+ 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",
1651
+ melody: "Electric Piano 1",
1652
+ submelody: "Flute",
1653
+ bass: "Acoustic Bass",
1654
+ chord: "Electric Guitar (jazz)"
1655
+ },
1656
+ // --- MODERN & VIBE: エッジの効いた現代的な響き ---
1657
+ synth_pop: {
1658
+ displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
1659
+ description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
1660
+ melody: "Lead 2 (sawtooth)",
1661
+ submelody: "Lead 4 (chiff)",
1662
+ bass: "Synth Bass 2",
1663
+ chord: "Pad 3 (polysynth)"
1664
+ },
1665
+ cyber_punk: {
1666
+ displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
1667
+ 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",
1668
+ melody: "Lead 8 (bass + lead)",
1669
+ submelody: "Lead 5 (charang)",
1670
+ bass: "Synth Bass 2",
1671
+ chord: "Pad 8 (sweep)"
1672
+ },
1673
+ rock: {
1674
+ displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
1675
+ description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
1676
+ melody: "Distortion Guitar",
1677
+ submelody: "Rock Organ",
1678
+ bass: "Electric Bass (pick)",
1679
+ chord: "Overdriven Guitar"
1680
+ },
1681
+ // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
1682
+ orchestra: {
1683
+ displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
1684
+ description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
1685
+ melody: "French Horn",
1686
+ submelody: "Pizzicato Strings",
1687
+ bass: "Cello",
1688
+ chord: "Tremolo Strings"
1689
+ },
1690
+ japanese_wa: {
1691
+ displayName: "\u548C\u98A8\u30FB\u96C5",
1692
+ description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
1693
+ melody: "Koto",
1694
+ submelody: "Shamisen",
1695
+ bass: "Taiko Drum",
1696
+ chord: "Shakuhachi"
1697
+ },
1698
+ arabic_exotic: {
1699
+ displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
1700
+ 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",
1701
+ melody: "Sitar",
1702
+ submelody: "Bagpipe",
1703
+ bass: "Fretless Bass",
1704
+ chord: "Kalimba"
1705
+ },
1706
+ // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
1707
+ fantasy_rpg: {
1708
+ displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
1709
+ description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
1710
+ melody: "Ocarina",
1711
+ submelody: "Celesta",
1712
+ bass: "Timpani",
1713
+ chord: "Orchestral Harp"
1714
+ },
1715
+ ambient_cloud: {
1716
+ displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
1717
+ description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
1718
+ melody: "Lead 6 (voice)",
1719
+ submelody: "Music Box",
1720
+ bass: "Synth Bass 1",
1721
+ chord: "Pad 7 (halo)"
1722
+ },
1723
+ retro_game: {
1724
+ displayName: "8-bit \u30EC\u30C8\u30ED",
1725
+ 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",
1726
+ melody: "Lead 1 (square)",
1727
+ submelody: "Lead 2 (sawtooth)",
1728
+ bass: "Synth Bass 1",
1729
+ chord: "Clavinet"
1730
+ }
1731
+ };
1732
+
1623
1733
  // node_modules/.pnpm/@onjmin+koe@1.0.3/node_modules/@onjmin/koe/dist/index.js
1624
1734
  var MAGIC = 1263486208;
1625
1735
  function parseKoeHeader(headerBytes) {
@@ -2502,7 +2612,20 @@ var createWorkerBackend = async (workerUrl, options) => {
2502
2612
  };
2503
2613
  };
2504
2614
  var createKoeVoice = async (ctx, destination, options) => {
2505
- const backend = options.voiceWorkerUrl ? await createWorkerBackend(options.voiceWorkerUrl, options) : await createLocalBackend(options);
2615
+ let backend;
2616
+ if (options.voiceWorkerUrl) {
2617
+ try {
2618
+ backend = await createWorkerBackend(options.voiceWorkerUrl, options);
2619
+ } catch (err2) {
2620
+ console.warn(
2621
+ "[dtm] Failed to spawn voice worker. Falling back to local backend.",
2622
+ err2
2623
+ );
2624
+ backend = await createLocalBackend(options);
2625
+ }
2626
+ } else {
2627
+ backend = await createLocalBackend(options);
2628
+ }
2506
2629
  const renderCache = /* @__PURE__ */ new Map();
2507
2630
  const inflight = /* @__PURE__ */ new Map();
2508
2631
  const active = /* @__PURE__ */ new Set();
@@ -4017,6 +4140,110 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
4017
4140
  return chordNotes / notes.length >= threshold;
4018
4141
  };
4019
4142
 
4143
+ // src/mml-info.ts
4144
+ var MML_INFO_HTML = `
4145
+ <div class="dtm-modal-body-content">
4146
+ <h4>1. \u97F3\u7B26\u3068\u4F11\u7B26</h4>
4147
+ <p><code>c</code>(\u30C9) <code>d</code>(\u30EC) <code>e</code>(\u30DF) <code>f</code>(\u30D5\u30A1) <code>g</code>(\u30BD) <code>a</code>(\u30E9) <code>b</code>(\u30B7) \u306E\u30A2\u30EB\u30D5\u30A1\u30D9\u30C3\u30C8\u3067\u8868\u3057\u307E\u3059\u3002</p>
4148
+ <ul>
4149
+ <li>\u534A\u97F3\u4E0A\u3052\u308B: <code>c#</code> \u307E\u305F\u306F <code>c+</code></li>
4150
+ <li>\u534A\u97F3\u4E0B\u3052\u308B: <code>d-</code></li>
4151
+ <li>\u4F11\u7B26: <code>r</code></li>
4152
+ </ul>
4153
+
4154
+ <h4>2. \u97F3\u306E\u9577\u3055</h4>
4155
+ <p>\u97F3\u540D\u3084\u4F11\u7B26\u306E\u5F8C\u306B\u6570\u5024\u3067\u6307\u5B9A\u3057\u307E\u3059\uFF08\u4F8B: <code>4</code> = 4\u5206\u97F3\u7B26, <code>8</code> = 8\u5206\u97F3\u7B26, <code>16</code> = 16\u5206\u97F3\u7B26\uFF09\u3002</p>
4156
+ <ul>
4157
+ <li><code>c4</code> : 4\u5206\u97F3\u7B26\u306E\u30C9</li>
4158
+ <li><code>r8</code> : 8\u5206\u4F11\u7B26</li>
4159
+ <li><code>c4.</code> : \u4ED8\u70B94\u5206\u97F3\u7B26\u306E\u30C9\uFF08\u9577\u3055\u30921.5\u500D\u306B\uFF09</li>
4160
+ <li>\u6570\u5024\u3092\u7701\u7565\u3059\u308B\u3068\u3001<code>l</code> \u30B3\u30DE\u30F3\u30C9\u3067\u8A2D\u5B9A\u3055\u308C\u305F\u30C7\u30D5\u30A9\u30EB\u30C8\u9577\uFF08\u901A\u5E3816\u5206\uFF09\u306B\u306A\u308A\u307E\u3059\u3002</li>
4161
+ </ul>
4162
+
4163
+ <h4>3. \u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u306E\u9AD8\u3055\uFF09</h4>
4164
+ <ul>
4165
+ <li><code>o4</code>, <code>o5</code> : \u9AD8\u3055\u3092\u76F4\u63A5\u6307\u5B9A\uFF08\u3075\u3064\u3046\u306F o4 \u304B o5\uFF09</li>
4166
+ <li><code>&gt;</code> : 1\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u308B</li>
4167
+ <li><code>&lt;</code> : 1\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\u3052\u308B</li>
4168
+ </ul>
4169
+
4170
+ <h4>4. \u30C6\u30F3\u30DD</h4>
4171
+ <ul>
4172
+ <li><code>t120</code> : \u66F2\u306E\u901F\u3055\u3092BPM120\u306B\u6307\u5B9A\u3002\u203B\u30E1\u30ED\u30C7\u30A3\uFF08@0\uFF09\u306E\u30C6\u30F3\u30DD\u6307\u5B9A\u304C\u66F2\u5168\u4F53\u306B\u53CD\u6620\u3055\u308C\u307E\u3059\u3002</li>
4173
+ </ul>
4174
+
4175
+ <h4>5. \u548C\u97F3</h4>
4176
+ <p>\u97F3\u7B26\u3092 <code>[</code> \u3068 <code>]</code> \u3067\u56F2\u3080\u3068\u540C\u6642\u306B\u767A\u97F3\u3057\u307E\u3059\u3002</p>
4177
+ <pre>\u4F8B: [ceg]4 \uFF08\u30C9\u30FB\u30DF\u30FB\u30BD\u30924\u5206\u97F3\u7B26\u3067\u540C\u6642\u306B\u767A\u97F3\uFF09</pre>
4178
+
4179
+ <h4>6. \u30C8\u30E9\u30C3\u30AF\u306E\u533A\u5207\u308A</h4>
4180
+ <p><code>;</code> \u307E\u305F\u306F <code>@0</code>\u301C<code>@3</code> \u3067\u30C8\u30E9\u30C3\u30AF\u3092\u5207\u308A\u66FF\u3048\u307E\u3059\u3002</p>
4181
+ <ul>
4182
+ <li><code>@0</code>: \u30E1\u30ED\u30C7\u30A3</li>
4183
+ <li><code>@1</code>: \u30B5\u30D6\u30E1\u30ED</li>
4184
+ <li><code>@2</code>: \u30D9\u30FC\u30B9</li>
4185
+ <li><code>@3</code>: \u4F34\u594F</li>
4186
+ </ul>
4187
+
4188
+ <h4>7. \u6B4C\u58F0\u30FB\u6B4C\u8A5E\u5165\u529B</h4>
4189
+ <p><code>@@&lt;\u30C8\u30E9\u30C3\u30AF\u756A\u53F7&gt; &lt;\u97F3\u6E90\u540D&gt; &lt;\u6B4C\u8A5E&gt;</code> \u306E\u5F62\u5F0F\u3067\u3001\u97F3\u7B26\u3068\u540C\u671F\u3059\u308B\u6B4C\u8A5E\u3092\u5165\u529B\u3067\u304D\u307E\u3059\u3002</p>
4190
+ <pre>\u4F8B: @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053</pre>
4191
+ <p style="margin-top:4px; margin-bottom:16px;"><small>\uFF08\u97F3\u6E90\u540D\u306F <code>tsukuyomi</code> \u3084 <code>klatt</code>, <code>roze</code> \u306A\u3069\u306E\u97F3\u58F0\u30E2\u30C7\u30EB\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\uFF09</small></p>
4192
+
4193
+ <h4 style="margin-top: 18px; border-top: 1px solid var(--dtm-border2); padding-top: 8px;">\u30B5\u30F3\u30D7\u30EB\u66F2\uFF08\u8A66\u8074\u30FB\u30B3\u30D4\u30FC\uFF09</h4>
4194
+
4195
+ <!-- \u30B5\u30F3\u30D7\u30EB1 -->
4196
+ <div class="dtm-modal-sample-box">
4197
+ <div class="dtm-modal-sample-header">
4198
+ <span class="dtm-modal-sample-tag">1. \u57FA\u672C\u306E\u30E1\u30ED\u30C7\u30A3</span>
4199
+ <button class="dtm-btn dtm-btn--ghost dtm-btn--xs dtm-modal-sample-copy-btn" data-mml="@0 t120 l8 o5 c d e f g a b > c">\u{1F4CB} \u30B3\u30D4\u30FC</button>
4200
+ </div>
4201
+ <pre style="margin: 0; padding: 6px;">@0 t120 l8 o5 c d e f g a b &gt; c</pre>
4202
+ <div class="dtm-modal-sample-desc">
4203
+ \u57FA\u672C\u7684\u306A\u30E1\u30ED\u30C7\u30A3\u306E\u66F8\u304D\u65B9\uFF08\u97F3\u540D\u30FB\u9577\u3055\u30FB\u30AA\u30AF\u30BF\u30FC\u30D6\u3068\u30C6\u30F3\u30DD\uFF09\u3002
4204
+ </div>
4205
+ <div style="margin-top: 8px;">
4206
+ <button class="dtm-btn dtm-btn--primary dtm-btn--xs dtm-modal-sample-play-btn" data-mml="@0 t120 l8 o5 c d e f g a b > c">\u25B6 \u8A66\u8074</button>
4207
+ </div>
4208
+ <div class="dtm-modal-sample-player-container"></div>
4209
+ </div>
4210
+
4211
+ <!-- \u30B5\u30F3\u30D7\u30EB2 -->
4212
+ <div class="dtm-modal-sample-box">
4213
+ <div class="dtm-modal-sample-header">
4214
+ <span class="dtm-modal-sample-tag">2. \u8907\u6570\u30C8\u30E9\u30C3\u30AF\u3068\u548C\u97F3</span>
4215
+ <button class="dtm-btn dtm-btn--ghost dtm-btn--xs dtm-modal-sample-copy-btn" data-mml="@0 t120 o5 c e g2 ; @3 o4 [ceg]2 [ceg]2">\u{1F4CB} \u30B3\u30D4\u30FC</button>
4216
+ </div>
4217
+ <pre style="margin: 0; padding: 6px;">@0 t120 o5 c e g2 ;
4218
+ @3 o4 [ceg]2 [ceg]2</pre>
4219
+ <div class="dtm-modal-sample-desc">
4220
+ ; \u3067\u30C8\u30E9\u30C3\u30AF\uFF08\u4E0A\uFF1D\u30E1\u30ED\u30C7\u30A3\uFF0F\u4E0B\uFF1D\u4F34\u594F\uFF09\u3092\u5206\u3051\u3001[ceg] \u3067\u548C\u97F3\u3092\u9CF4\u3089\u3057\u307E\u3059\u3002
4221
+ </div>
4222
+ <div style="margin-top: 8px;">
4223
+ <button class="dtm-btn dtm-btn--primary dtm-btn--xs dtm-modal-sample-play-btn" data-mml="@0 t120 o5 c e g2 ; @3 o4 [ceg]2 [ceg]2">\u25B6 \u8A66\u8074</button>
4224
+ </div>
4225
+ <div class="dtm-modal-sample-player-container"></div>
4226
+ </div>
4227
+
4228
+ <!-- \u30B5\u30F3\u30D7\u30EB3 -->
4229
+ <div class="dtm-modal-sample-box">
4230
+ <div class="dtm-modal-sample-header">
4231
+ <span class="dtm-modal-sample-tag">3. \u6B4C\u5531\u4ED8\u304D (\u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D)</span>
4232
+ <button class="dtm-btn dtm-btn--ghost dtm-btn--xs dtm-modal-sample-copy-btn" data-mml="@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.; @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053;">\u{1F4CB} \u30B3\u30D4\u30FC</button>
4233
+ </div>
4234
+ <pre style="margin: 0; padding: 6px;">@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.;
4235
+ @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053;</pre>
4236
+ <div class="dtm-modal-sample-desc">
4237
+ @@0 tsukuyomi \u6B4C\u8A5E... \u3067\u30E1\u30ED\u30C7\u30A3\u30C8\u30E9\u30C3\u30AF\u306B\u6B4C\u8A5E\u3092\u540C\u671F\u3055\u305B\u3066\u6B4C\u308F\u305B\u307E\u3059\u3002\u203B\u72EC\u81EA\u62E1\u5F35
4238
+ </div>
4239
+ <div style="margin-top: 8px;">
4240
+ <button class="dtm-btn dtm-btn--primary dtm-btn--xs dtm-modal-sample-play-btn" data-mml="@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.; @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053;">\u25B6 \u8A66\u8074</button>
4241
+ </div>
4242
+ <div class="dtm-modal-sample-player-container"></div>
4243
+ </div>
4244
+ </div>
4245
+ `;
4246
+
4020
4247
  // src/mml-parser.ts
4021
4248
  var PITCH_MAP2 = {
4022
4249
  c: 0,
@@ -4069,6 +4296,7 @@ var parseMML = (mml, options = {}) => {
4069
4296
  bpm,
4070
4297
  tokenTracks: collectTokens ? tokenTracks : void 0,
4071
4298
  lyrics: collectLyrics ? /* @__PURE__ */ new Map() : void 0,
4299
+ mergedTrackCount: 0,
4072
4300
  meta: {}
4073
4301
  };
4074
4302
  }
@@ -4081,13 +4309,24 @@ var parseMML = (mml, options = {}) => {
4081
4309
  const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
4082
4310
  const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
4083
4311
  let trackIndex = 0;
4312
+ let sourceTrackIndex = 0;
4084
4313
  let octave = 4;
4085
4314
  let currentStep = 0;
4086
4315
  let baseLength = 16;
4316
+ const contributors = /* @__PURE__ */ new Map();
4317
+ const recordContributor = () => {
4318
+ let set = contributors.get(trackIndex);
4319
+ if (!set) {
4320
+ set = /* @__PURE__ */ new Set();
4321
+ contributors.set(trackIndex, set);
4322
+ }
4323
+ set.add(sourceTrackIndex);
4324
+ };
4087
4325
  for (const rawPart of parts) {
4088
4326
  const part = rawPart.trim();
4089
4327
  if (part.startsWith("@")) {
4090
4328
  let idx = Number.parseInt(part.substring(1), 10);
4329
+ sourceTrackIndex = idx;
4091
4330
  if (clampTrackCount !== void 0 && idx >= clampTrackCount)
4092
4331
  idx = clampTrackCount - 1;
4093
4332
  trackIndex = idx;
@@ -4211,6 +4450,7 @@ var parseMML = (mml, options = {}) => {
4211
4450
  }
4212
4451
  if (j < body.length && body[j] === "]") j++;
4213
4452
  const steps = parseLength();
4453
+ if (chordNotes.length > 0) recordContributor();
4214
4454
  for (const p of chordNotes) {
4215
4455
  placements.push({
4216
4456
  trackIndex,
@@ -4234,6 +4474,7 @@ var parseMML = (mml, options = {}) => {
4234
4474
  }
4235
4475
  const midiPitch = (octave + 1) * 12 + pitch;
4236
4476
  const steps = parseLength();
4477
+ recordContributor();
4237
4478
  placements.push({
4238
4479
  trackIndex,
4239
4480
  startStep: currentStep,
@@ -4247,11 +4488,16 @@ var parseMML = (mml, options = {}) => {
4247
4488
  }
4248
4489
  }
4249
4490
  }
4491
+ let mergedTrackCount = 0;
4492
+ for (const set of contributors.values()) {
4493
+ if (set.size >= 2) mergedTrackCount++;
4494
+ }
4250
4495
  return {
4251
4496
  placements,
4252
4497
  bpm,
4253
4498
  tokenTracks: collectTokens ? tokenTracks : void 0,
4254
4499
  lyrics,
4500
+ mergedTrackCount,
4255
4501
  meta
4256
4502
  };
4257
4503
  };
@@ -4313,6 +4559,8 @@ var createSequencer = (options) => {
4313
4559
  let active = false;
4314
4560
  let fromStepValue = 0;
4315
4561
  let trackVolumeMap = /* @__PURE__ */ new Map();
4562
+ let lastRealTime = -1;
4563
+ let lastAudioTime = -1;
4316
4564
  let isLooping = false;
4317
4565
  let loopStartStep = 0;
4318
4566
  let loopEndStep = 0;
@@ -4386,6 +4634,21 @@ var createSequencer = (options) => {
4386
4634
  const sps = secondsPerStep();
4387
4635
  const time = options.getAudioTime() - startTime;
4388
4636
  const soloId = options.getSoloTrackId();
4637
+ const nowReal = performance.now() / 1e3;
4638
+ if (lastRealTime > 0 && lastAudioTime >= 0) {
4639
+ const realDelta = nowReal - lastRealTime;
4640
+ const audioDelta = time - lastAudioTime;
4641
+ if (realDelta > 0.5 || audioDelta > 0.5) {
4642
+ console.warn(
4643
+ `[sequencer] Interruption detected (realDelta: ${realDelta.toFixed(3)}s, audioDelta: ${audioDelta.toFixed(3)}s). Stopping playback.`
4644
+ );
4645
+ stop();
4646
+ options.onEnd(true);
4647
+ return;
4648
+ }
4649
+ }
4650
+ lastRealTime = nowReal;
4651
+ lastAudioTime = time;
4389
4652
  for (const track of options.getTracks()) {
4390
4653
  trackVolumeMap.set(track.id, track.volume);
4391
4654
  }
@@ -4462,7 +4725,7 @@ var createSequencer = (options) => {
4462
4725
  const lastDuration = last?.duration ?? 0;
4463
4726
  if (nowIndex >= timeline.length && time > lastWhen + lastDuration + 0.1) {
4464
4727
  stop();
4465
- options.onEnd();
4728
+ options.onEnd(false);
4466
4729
  }
4467
4730
  }
4468
4731
  };
@@ -4503,6 +4766,8 @@ var createSequencer = (options) => {
4503
4766
  }
4504
4767
  loopBase = 0;
4505
4768
  lastPlayStep = fromStepValue - 1e-4;
4769
+ lastRealTime = -1;
4770
+ lastAudioTime = -1;
4506
4771
  intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
4507
4772
  animationId = requestAnimationFrame(animate);
4508
4773
  };
@@ -4601,9 +4866,13 @@ var DAW_CSS = `
4601
4866
  /* \u30C7\u30B6\u30A4\u30F3\u30C8\u30FC\u30AF\u30F3\u306F\u7DE8\u96C6UI\u672C\u4F53\uFF08.dtm-daw\uFF09\u306B\u52A0\u3048\u3001\u305D\u306E\u5916\u5074\u306B\u5DEE\u3057\u8FBC\u307E\u308C\u308B
4602
4867
  \u30B3\u30F3\u30C8\u30ED\u30FC\u30EB\u30D0\u30FC\uFF08.dtm-controlbar\uFF09\u306B\u3082\u4F9B\u7D66\u3059\u308B\u3002mountPresetSelect /
4603
4868
  mountModeSwitch \u306EUI\u306F .dtm-daw \u306E\u5144\u5F1F\u3068\u3057\u3066\u7F6E\u304B\u308C\u308B\u305F\u3081\u3001\u3053\u3053\u3067\u914D\u3089\u306A\u3044\u3068
4604
- var(--dtm-*) \u304C\u89E3\u6C7A\u3067\u304D\u305A\u7121\u88C5\u98FE\uFF08\u767D\u5730\u30FB\u65E2\u5B9A\u30D5\u30A9\u30F3\u30C8\uFF09\u306B\u306A\u3063\u3066\u3057\u307E\u3046\u3002 */
4869
+ var(--dtm-*) \u304C\u89E3\u6C7A\u3067\u304D\u305A\u7121\u88C5\u98FE\uFF08\u767D\u5730\u30FB\u65E2\u5B9A\u30D5\u30A9\u30F3\u30C8\uFF09\u306B\u306A\u3063\u3066\u3057\u307E\u3046\u3002
4870
+ \u518D\u751F\u5C02\u7528\u30D3\u30E5\u30FC\u306E\u30E2\u30FC\u30C0\u30EB\uFF0F\u5229\u7528\u898F\u7D04\u30AB\u30D0\u30FC\u306F document.body \u76F4\u4E0B\u3078\u91CD\u306D\u308B\u305F\u3081\u3001
4871
+ .dtm-daw \u306E\u5916\u306B\u51FA\u308B\u3002\u3053\u308C\u3089\u3082\u540C\u69D8\u306B\u30C8\u30FC\u30AF\u30F3\u3092\u4F9B\u7D66\u3057\u306A\u3044\u3068\u9ED2\u5730\u30FB\u767D\u6587\u5B57\u306B\u306A\u308B\u3002 */
4605
4872
  .dtm-daw,
4606
- .dtm-controlbar {
4873
+ .dtm-controlbar,
4874
+ .dtm-modal-overlay,
4875
+ .dtm-consent-overlay {
4607
4876
  /* PICO-8 16\u8272\u30D1\u30EC\u30C3\u30C8\u3088\u308A */
4608
4877
  --c-black: #000000;
4609
4878
  --c-navy: #1d2b53;
@@ -4700,11 +4969,11 @@ var DAW_CSS = `
4700
4969
  }
4701
4970
  .dtm-btn:active { transform: translate(3px,3px); box-shadow: none; }
4702
4971
  .dtm-btn:disabled { opacity: .3; cursor: default; box-shadow: none; }
4972
+ .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4703
4973
  .dtm-btn--primary { border-color: var(--dtm-primary); background: var(--dtm-primary); color: var(--dtm-pfg); }
4704
4974
  .dtm-btn--success { border-color: var(--dtm-success); background: var(--dtm-success); color: var(--c-black); }
4705
4975
  .dtm-btn--danger { border-color: var(--dtm-danger); background: var(--dtm-danger); color: var(--c-white); }
4706
4976
  .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
4977
  .dtm-btn--icon { padding: 0; }
4709
4978
 
4710
4979
  /* \u2500\u2500\u2500 \u30A2\u30A4\u30B3\u30F3\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
@@ -5201,14 +5470,37 @@ var DAW_CSS = `
5201
5470
  justify-content: center;
5202
5471
  padding: 16px;
5203
5472
  backdrop-filter: blur(2px);
5473
+ /* body\u76F4\u4E0B\u306B\u91CD\u306D\u305F\u5834\u5408\uFF08\u518D\u751F\u5C02\u7528\u30D3\u30E5\u30FC\uFF09\u3067\u3082\u6587\u5B57\u8272\u30FB\u30D5\u30A9\u30F3\u30C8\u304C
5474
+ .dtm-daw \u304B\u3089\u7D99\u627F\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u3053\u3053\u3067\u660E\u793A\u3059\u308B\u3002 */
5475
+ color: var(--dtm-text);
5476
+ font-family: var(--dtm-font);
5204
5477
  }
5205
5478
  .dtm-modal-overlay[hidden] {
5206
5479
  display: none !important;
5207
5480
  }
5208
- .dtm-modal {
5209
- max-width: 500px;
5481
+
5482
+ /* \u2500\u2500\u2500 \u5229\u7528\u898F\u7D04\u540C\u610F\u30AB\u30D0\u30FC \u2500\u2500\u2500 */
5483
+ .dtm-consent-overlay {
5484
+ position: fixed;
5485
+ inset: 0;
5486
+ z-index: 10100;
5487
+ background: rgba(0, 0, 0, 0.6);
5488
+ display: flex;
5489
+ align-items: center;
5490
+ justify-content: center;
5491
+ padding: 16px;
5492
+ backdrop-filter: blur(2px);
5493
+ /* body\u76F4\u4E0B\u306B\u91CD\u306D\u308B\u305F\u3081 .dtm-daw \u304B\u3089\u7D99\u627F\u3067\u304D\u306A\u3044\u6587\u5B57\u8272\u30FB\u30D5\u30A9\u30F3\u30C8\u3092\u660E\u793A\u3002 */
5494
+ color: var(--dtm-text);
5495
+ font-family: var(--dtm-font);
5496
+ }
5497
+ .dtm-consent-overlay[hidden] {
5498
+ display: none !important;
5499
+ }
5500
+ .dtm-consent-modal {
5501
+ max-width: 450px;
5210
5502
  width: 100%;
5211
- max-height: 80vh;
5503
+ max-height: 85vh;
5212
5504
  display: flex;
5213
5505
  flex-direction: column;
5214
5506
  background: var(--dtm-surface);
@@ -5217,20 +5509,63 @@ var DAW_CSS = `
5217
5509
  inset 0 0 0 2px var(--c-black),
5218
5510
  0 0 0 2px var(--dtm-primary),
5219
5511
  4px 4px 0 var(--c-black);
5220
- overflow: hidden;
5512
+ overflow-y: auto;
5221
5513
  }
5222
- .dtm-modal-header {
5223
- display: flex;
5224
- align-items: center;
5225
- justify-content: space-between;
5514
+ .dtm-consent-header {
5226
5515
  background: var(--dtm-deep);
5516
+ color: var(--dtm-text);
5227
5517
  padding: 8px 12px;
5228
5518
  border-bottom: 2px solid var(--c-black);
5229
- }
5230
- .dtm-modal-title {
5231
- font-family: var(--dtm-font);
5232
- font-size: 14px;
5233
- color: var(--dtm-gold);
5519
+ font-weight: bold;
5520
+ text-align: center;
5521
+ font-size: 14px;
5522
+ }
5523
+ .dtm-consent-body {
5524
+ padding: 12px 16px;
5525
+ font-size: 13px;
5526
+ line-height: 1.6;
5527
+ }
5528
+ .dtm-consent-body a {
5529
+ color: var(--dtm-primary);
5530
+ text-decoration: underline;
5531
+ }
5532
+ .dtm-consent-body a:hover {
5533
+ color: var(--dtm-accent);
5534
+ }
5535
+ .dtm-consent-footer {
5536
+ padding: 8px;
5537
+ border-top: 2px solid var(--c-black);
5538
+ background: var(--dtm-deep);
5539
+ display: flex;
5540
+ justify-content: center;
5541
+ }
5542
+
5543
+ .dtm-modal {
5544
+ max-width: 500px;
5545
+ width: 100%;
5546
+ max-height: 80vh;
5547
+ display: flex;
5548
+ flex-direction: column;
5549
+ background: var(--dtm-surface);
5550
+ border: 2px solid var(--c-black);
5551
+ box-shadow:
5552
+ inset 0 0 0 2px var(--c-black),
5553
+ 0 0 0 2px var(--dtm-primary),
5554
+ 4px 4px 0 var(--c-black);
5555
+ overflow: hidden;
5556
+ }
5557
+ .dtm-modal-header {
5558
+ display: flex;
5559
+ align-items: center;
5560
+ justify-content: space-between;
5561
+ background: var(--dtm-deep);
5562
+ padding: 8px 12px;
5563
+ border-bottom: 2px solid var(--c-black);
5564
+ }
5565
+ .dtm-modal-title {
5566
+ font-family: var(--dtm-font);
5567
+ font-size: 14px;
5568
+ color: var(--dtm-gold);
5234
5569
  font-weight: bold;
5235
5570
  }
5236
5571
  .dtm-modal-close {
@@ -5338,6 +5673,19 @@ var DAW_CSS = `
5338
5673
  }
5339
5674
 
5340
5675
  .dtm-hidden { display: none !important; }
5676
+ /* \u8AAD\u8FBC\u6642\u306E\u8B66\u544A\u304A\u77E5\u3089\u305B\uFF08\u4F8B: \u30B7\u30F3\u30D7\u30EB\u30E2\u30FC\u30C9\u3067\u306E\u30C8\u30E9\u30C3\u30AF\u5408\u7B97\uFF09\u3002 */
5677
+ .dtm-load-note {
5678
+ margin: 6px 0 0;
5679
+ padding: 0 2px;
5680
+ font-family: var(--dtm-font);
5681
+ font-size: 11px;
5682
+ line-height: 1.5;
5683
+ letter-spacing: .04em;
5684
+ color: var(--dtm-warn); /* \u8B66\u544A\u8272\uFF08\u30AA\u30EC\u30F3\u30B8\uFF09 */
5685
+ font-weight: bold;
5686
+ opacity: 1.0;
5687
+ }
5688
+ .dtm-load-note::before { content: "\u26A0 "; }
5341
5689
  .dtm-grow { flex: 1 1 auto; }
5342
5690
  .dtm-lyric-icon {
5343
5691
  flex: 0 0 auto;
@@ -5437,6 +5785,62 @@ var DAW_CSS = `
5437
5785
  white-space: nowrap;
5438
5786
  }
5439
5787
  .dtm-player-mml-link:hover { color: var(--dtm-primary); }
5788
+ .dtm-player-more-container {
5789
+ position: relative;
5790
+ display: inline-flex;
5791
+ align-items: center;
5792
+ }
5793
+ .dtm-player-more-btn {
5794
+ background: transparent;
5795
+ border: none;
5796
+ color: var(--dtm-muted);
5797
+ cursor: pointer;
5798
+ padding: 2px;
5799
+ display: flex;
5800
+ align-items: center;
5801
+ justify-content: center;
5802
+ border-radius: 50%;
5803
+ width: 24px;
5804
+ height: 24px;
5805
+ transition: color 0.15s, background-color 0.15s;
5806
+ }
5807
+ .dtm-player-more-btn:hover,
5808
+ .dtm-player-more-btn.is-active {
5809
+ color: var(--dtm-text);
5810
+ background: var(--dtm-border2);
5811
+ }
5812
+ .dtm-player-menu {
5813
+ position: absolute;
5814
+ top: calc(100% + 4px);
5815
+ right: 0;
5816
+ background: var(--dtm-deep);
5817
+ border: 2px solid var(--dtm-border2);
5818
+ box-shadow: 4px 4px 0 var(--c-black);
5819
+ z-index: 200;
5820
+ display: flex;
5821
+ flex-direction: column;
5822
+ padding: 4px 0;
5823
+ min-width: 130px;
5824
+ font-family: var(--dtm-font);
5825
+ }
5826
+ .dtm-player-menu-item {
5827
+ background: transparent;
5828
+ border: none;
5829
+ color: var(--dtm-text);
5830
+ padding: 6px 12px;
5831
+ text-align: left;
5832
+ cursor: pointer;
5833
+ font-size: 11px;
5834
+ font-family: inherit;
5835
+ white-space: nowrap;
5836
+ width: 100%;
5837
+ box-sizing: border-box;
5838
+ transition: background-color 0.1s, color 0.1s;
5839
+ }
5840
+ .dtm-player-menu-item:hover {
5841
+ background: var(--dtm-primary);
5842
+ color: var(--dtm-pfg);
5843
+ }
5440
5844
  .dtm-player-emoji {
5441
5845
  position: relative;
5442
5846
  display: inline-flex;
@@ -5682,6 +6086,7 @@ var STEPS_PER_BEAT3 = 48;
5682
6086
  var STEPS_PER_BAR = 192;
5683
6087
  var DEFAULT_TRACK_COLORS = ["#00e436", "#29adff", "#ff77a8", "#ffec27"];
5684
6088
  var activePlayer = null;
6089
+ var agreedModelsInSession = /* @__PURE__ */ new Set();
5685
6090
  var LYRIC_MODEL_LABELS = {
5686
6091
  klatt: "\u8EFD\u91CF\u30ED\u30DC\u58F0",
5687
6092
  ...KOE_VOICEBANK_LABELS
@@ -5712,6 +6117,75 @@ var showBalloon = (balloonEl) => {
5712
6117
  hideActiveBalloon();
5713
6118
  }, 3e3);
5714
6119
  };
6120
+ var copyToClipboard = async (doc, text) => {
6121
+ try {
6122
+ await navigator.clipboard.writeText(text);
6123
+ return true;
6124
+ } catch {
6125
+ try {
6126
+ const textarea = doc.createElement("textarea");
6127
+ textarea.value = text;
6128
+ textarea.style.position = "fixed";
6129
+ textarea.style.opacity = "0";
6130
+ doc.body.appendChild(textarea);
6131
+ textarea.select();
6132
+ const ok = doc.execCommand("copy");
6133
+ doc.body.removeChild(textarea);
6134
+ return ok;
6135
+ } catch {
6136
+ return false;
6137
+ }
6138
+ }
6139
+ };
6140
+ var toBase64Url = (bytes) => {
6141
+ let bin = "";
6142
+ for (let i = 0; i < bytes.length; i++) {
6143
+ bin += String.fromCharCode(bytes[i]);
6144
+ }
6145
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6146
+ };
6147
+ var HIRAGANA_START = 12353;
6148
+ var HIRAGANA_END = 12447;
6149
+ var KATAKANA_START = 12449;
6150
+ var KATAKANA_END = 12543;
6151
+ var PROLONGED_MARK = 12540;
6152
+ var SHIFT_KATAKANA = 255;
6153
+ var VALUE_PROLONGED = 223;
6154
+ var customEncode = (str) => {
6155
+ const bytes = [];
6156
+ for (let i = 0; i < str.length; i++) {
6157
+ const code = str.charCodeAt(i);
6158
+ if (code <= 127) {
6159
+ bytes.push(code);
6160
+ } else if (code === PROLONGED_MARK) {
6161
+ bytes.push(VALUE_PROLONGED);
6162
+ } else if (code >= HIRAGANA_START && code <= HIRAGANA_END) {
6163
+ bytes.push(128 + (code - HIRAGANA_START));
6164
+ } else if (code >= KATAKANA_START && code <= KATAKANA_END) {
6165
+ bytes.push(SHIFT_KATAKANA);
6166
+ bytes.push(128 + (code - 96 - HIRAGANA_START));
6167
+ }
6168
+ }
6169
+ return new Uint8Array(bytes);
6170
+ };
6171
+ var encodeMml = async (mml) => {
6172
+ try {
6173
+ if (typeof CompressionStream !== "undefined") {
6174
+ const cs = new CompressionStream("gzip");
6175
+ const w = cs.writable.getWriter();
6176
+ w.write(customEncode(mml));
6177
+ w.close();
6178
+ const buf = await new Response(cs.readable).arrayBuffer();
6179
+ return `z.${toBase64Url(new Uint8Array(buf))}`;
6180
+ }
6181
+ } catch (e) {
6182
+ console.warn(
6183
+ "[dtm] CompressionStream failed, fallback to encodeURIComponent",
6184
+ e
6185
+ );
6186
+ }
6187
+ return `u.${encodeURIComponent(mml)}`;
6188
+ };
5715
6189
  var mountMmlPlayer = (target, mml, options = {}) => {
5716
6190
  injectStyles(target.ownerDocument ?? document);
5717
6191
  const {
@@ -5857,6 +6331,250 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5857
6331
  emojiEls.push(em);
5858
6332
  emojiByTrack.set(index, em);
5859
6333
  }
6334
+ const menuContainer = doc.createElement("div");
6335
+ menuContainer.className = "dtm-player-more-container";
6336
+ const moreBtn = doc.createElement("button");
6337
+ moreBtn.type = "button";
6338
+ moreBtn.className = "dtm-player-more-btn";
6339
+ moreBtn.innerHTML = icon("more", 14);
6340
+ moreBtn.title = "\u30E1\u30CB\u30E5\u30FC";
6341
+ menuContainer.appendChild(moreBtn);
6342
+ const menuDropdown = doc.createElement("div");
6343
+ menuDropdown.className = "dtm-player-menu";
6344
+ menuDropdown.style.display = "none";
6345
+ const makeMenuItem = (label) => {
6346
+ const item = doc.createElement("button");
6347
+ item.type = "button";
6348
+ item.className = "dtm-player-menu-item";
6349
+ item.textContent = label;
6350
+ return item;
6351
+ };
6352
+ const showMmlItem = makeMenuItem("MML\u3092\u8868\u793A");
6353
+ const mmlInfoItem = makeMenuItem("MML\u66F8\u5F0F\u3068\u306F");
6354
+ const embedItem = makeMenuItem("\u57CB\u3081\u8FBC\u3080");
6355
+ const copyMmlItem = makeMenuItem("MML\u30B3\u30D4\u30FC");
6356
+ menuDropdown.appendChild(showMmlItem);
6357
+ menuDropdown.appendChild(mmlInfoItem);
6358
+ menuDropdown.appendChild(embedItem);
6359
+ menuDropdown.appendChild(copyMmlItem);
6360
+ menuContainer.appendChild(menuDropdown);
6361
+ mmlHeader.appendChild(menuContainer);
6362
+ const toggleMenu = (show) => {
6363
+ const visible = show !== void 0 ? show : menuDropdown.style.display === "none";
6364
+ menuDropdown.style.display = visible ? "flex" : "none";
6365
+ if (visible) {
6366
+ moreBtn.classList.add("is-active");
6367
+ doc.addEventListener("click", handleOutsideClick);
6368
+ } else {
6369
+ moreBtn.classList.remove("is-active");
6370
+ doc.removeEventListener("click", handleOutsideClick);
6371
+ }
6372
+ };
6373
+ const handleOutsideClick = (e) => {
6374
+ if (!menuContainer.contains(e.target)) {
6375
+ toggleMenu(false);
6376
+ }
6377
+ };
6378
+ moreBtn.addEventListener("click", (e) => {
6379
+ e.stopPropagation();
6380
+ toggleMenu();
6381
+ });
6382
+ let infoModalEl = null;
6383
+ let modalSamplePlayer = null;
6384
+ const closeInfoModal = () => {
6385
+ if (modalSamplePlayer) {
6386
+ modalSamplePlayer.stop();
6387
+ modalSamplePlayer.destroy();
6388
+ modalSamplePlayer = null;
6389
+ }
6390
+ infoModalEl?.remove();
6391
+ infoModalEl = null;
6392
+ };
6393
+ const openInfoModal = (title) => {
6394
+ closeInfoModal();
6395
+ const overlay = doc.createElement("div");
6396
+ overlay.className = "dtm-modal-overlay";
6397
+ const modal = doc.createElement("div");
6398
+ modal.className = "dtm-win dtm-modal";
6399
+ const header = doc.createElement("div");
6400
+ header.className = "dtm-modal-header";
6401
+ const titleEl = doc.createElement("span");
6402
+ titleEl.className = "dtm-modal-title";
6403
+ titleEl.textContent = title;
6404
+ const closeBtn = doc.createElement("button");
6405
+ closeBtn.type = "button";
6406
+ closeBtn.className = "dtm-modal-close";
6407
+ closeBtn.innerHTML = "&times;";
6408
+ closeBtn.title = "\u9589\u3058\u308B";
6409
+ header.append(titleEl, closeBtn);
6410
+ const modalBody = doc.createElement("div");
6411
+ modalBody.className = "dtm-modal-body";
6412
+ modal.append(header, modalBody);
6413
+ overlay.appendChild(modal);
6414
+ closeBtn.addEventListener("click", (e) => {
6415
+ e.stopPropagation();
6416
+ closeInfoModal();
6417
+ });
6418
+ overlay.addEventListener("click", (e) => {
6419
+ if (e.target === overlay) closeInfoModal();
6420
+ });
6421
+ doc.body.appendChild(overlay);
6422
+ infoModalEl = overlay;
6423
+ return modalBody;
6424
+ };
6425
+ const appendCopyButton = (parent, text) => {
6426
+ const actions = doc.createElement("div");
6427
+ actions.style.marginTop = "8px";
6428
+ const copyBtn = doc.createElement("button");
6429
+ copyBtn.type = "button";
6430
+ copyBtn.className = "dtm-btn dtm-btn--primary dtm-btn--xs";
6431
+ copyBtn.textContent = "\u{1F4CB} \u30B3\u30D4\u30FC";
6432
+ copyBtn.addEventListener("click", async (e) => {
6433
+ e.stopPropagation();
6434
+ const ok = await copyToClipboard(doc, text);
6435
+ copyBtn.textContent = ok ? "\u2713 \u30B3\u30D4\u30FC\u5B8C\u4E86" : "\u30B3\u30D4\u30FC\u5931\u6557";
6436
+ if (ok) copyBtn.classList.add("dtm-btn--success");
6437
+ setTimeout(() => {
6438
+ copyBtn.textContent = "\u{1F4CB} \u30B3\u30D4\u30FC";
6439
+ copyBtn.classList.remove("dtm-btn--success");
6440
+ }, 1200);
6441
+ });
6442
+ actions.appendChild(copyBtn);
6443
+ parent.appendChild(actions);
6444
+ };
6445
+ const wireSampleButtons = (modalBody) => {
6446
+ const copyBtns = modalBody.querySelectorAll(".dtm-modal-sample-copy-btn");
6447
+ for (const btn of copyBtns) {
6448
+ const el = btn;
6449
+ el.addEventListener("click", async (e) => {
6450
+ e.stopPropagation();
6451
+ const sampleMml = el.getAttribute("data-mml") ?? "";
6452
+ const original = el.textContent;
6453
+ const ok = await copyToClipboard(doc, sampleMml);
6454
+ el.textContent = ok ? "\u2713 \u30B3\u30D4\u30FC\u5B8C\u4E86" : "\u30B3\u30D4\u30FC\u5931\u6557";
6455
+ if (ok) el.classList.add("dtm-btn--success");
6456
+ setTimeout(() => {
6457
+ el.textContent = original;
6458
+ el.classList.remove("dtm-btn--success");
6459
+ }, 1200);
6460
+ });
6461
+ }
6462
+ let activeSampleBtn = null;
6463
+ const resetSampleBtn = (b) => {
6464
+ if (!b) return;
6465
+ b.textContent = "\u25B6 \u8A66\u8074";
6466
+ b.classList.remove("dtm-btn--danger");
6467
+ b.classList.add("dtm-btn--primary");
6468
+ };
6469
+ const markPlaying = (b) => {
6470
+ b.textContent = "\u25A0 \u505C\u6B62";
6471
+ b.classList.remove("dtm-btn--primary");
6472
+ b.classList.add("dtm-btn--danger");
6473
+ };
6474
+ const playBtns = modalBody.querySelectorAll(".dtm-modal-sample-play-btn");
6475
+ for (const btn of playBtns) {
6476
+ const el = btn;
6477
+ el.addEventListener("click", (e) => {
6478
+ e.stopPropagation();
6479
+ const sampleMml = el.getAttribute("data-mml") ?? "";
6480
+ if (activeSampleBtn === el && modalSamplePlayer) {
6481
+ if (modalSamplePlayer.isPlaying()) {
6482
+ modalSamplePlayer.stop();
6483
+ } else {
6484
+ modalSamplePlayer.play();
6485
+ markPlaying(el);
6486
+ }
6487
+ return;
6488
+ }
6489
+ if (modalSamplePlayer) {
6490
+ modalSamplePlayer.stop();
6491
+ modalSamplePlayer.destroy();
6492
+ modalSamplePlayer = null;
6493
+ }
6494
+ resetSampleBtn(activeSampleBtn);
6495
+ activeSampleBtn = el;
6496
+ const sampleBox = el.closest(".dtm-modal-sample-box");
6497
+ const container = sampleBox?.querySelector(
6498
+ ".dtm-modal-sample-player-container"
6499
+ );
6500
+ if (!container) return;
6501
+ container.innerHTML = "";
6502
+ modalSamplePlayer = mountMmlPlayer(container, sampleMml, {
6503
+ onPlayNote: options.onPlayNote,
6504
+ onPlayDrum: options.onPlayDrum,
6505
+ onResumeAudio: options.onResumeAudio,
6506
+ getAudioTime: options.getAudioTime,
6507
+ singingVoices: options.singingVoices,
6508
+ drumPatterns: options.drumPatterns,
6509
+ volume: trackVolume,
6510
+ // 解説モーダル内の試聴サンプルは規約同意を要求しない。
6511
+ skipConsent: true,
6512
+ onStop: () => {
6513
+ if (activeSampleBtn === el) resetSampleBtn(el);
6514
+ }
6515
+ });
6516
+ markPlaying(el);
6517
+ modalSamplePlayer.play();
6518
+ });
6519
+ }
6520
+ };
6521
+ showMmlItem.addEventListener("click", (e) => {
6522
+ e.stopPropagation();
6523
+ toggleMenu(false);
6524
+ const modalBody = openInfoModal("MML");
6525
+ const pre = doc.createElement("pre");
6526
+ pre.textContent = mml;
6527
+ pre.style.whiteSpace = "pre-wrap";
6528
+ pre.style.wordBreak = "break-all";
6529
+ modalBody.appendChild(pre);
6530
+ appendCopyButton(modalBody, mml);
6531
+ });
6532
+ mmlInfoItem.addEventListener("click", (e) => {
6533
+ e.stopPropagation();
6534
+ toggleMenu(false);
6535
+ const modalBody = openInfoModal("MML\u306E\u66F8\u304D\u65B9\u89E3\u8AAC");
6536
+ modalBody.innerHTML = MML_INFO_HTML;
6537
+ wireSampleButtons(modalBody);
6538
+ });
6539
+ embedItem.addEventListener("click", async (e) => {
6540
+ e.stopPropagation();
6541
+ toggleMenu(false);
6542
+ const modalBody = openInfoModal("\u57CB\u3081\u8FBC\u307F");
6543
+ const loading = doc.createElement("p");
6544
+ loading.textContent = "\u751F\u6210\u4E2D...";
6545
+ modalBody.appendChild(loading);
6546
+ try {
6547
+ const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6548
+ const payload = await encodeMml(mml);
6549
+ const url2 = `${embedBase}#${payload}`;
6550
+ const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6551
+ if (!modalBody.isConnected) return;
6552
+ loading.remove();
6553
+ const desc = doc.createElement("p");
6554
+ desc.textContent = "\u3053\u306EHTML\u3092\u30D6\u30ED\u30B0\u3084\u30B5\u30A4\u30C8\u306B\u8CBC\u308A\u4ED8\u3051\u308B\u3068\u3001\u30D7\u30EC\u30A4\u30E4\u30FC\u3092\u305D\u306E\u307E\u307E\u57CB\u3081\u8FBC\u3081\u307E\u3059\u3002";
6555
+ const pre = doc.createElement("pre");
6556
+ pre.textContent = snippet;
6557
+ pre.style.whiteSpace = "pre-wrap";
6558
+ pre.style.wordBreak = "break-all";
6559
+ modalBody.append(desc, pre);
6560
+ appendCopyButton(modalBody, snippet);
6561
+ } catch (err2) {
6562
+ console.error("[dtm] failed to generate embed snippet", err2);
6563
+ if (modalBody.isConnected) loading.textContent = "\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F";
6564
+ }
6565
+ });
6566
+ copyMmlItem.addEventListener("click", async (e) => {
6567
+ e.stopPropagation();
6568
+ const success = await copyToClipboard(doc, mml);
6569
+ if (success) {
6570
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F\uFF01";
6571
+ } else {
6572
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u5931\u6557";
6573
+ }
6574
+ setTimeout(() => {
6575
+ copyMmlItem.textContent = "MML\u30B3\u30D4\u30FC";
6576
+ }, 2e3);
6577
+ });
5860
6578
  const promotedToImage = /* @__PURE__ */ new Set();
5861
6579
  for (const [index, lt] of lyricTracks) {
5862
6580
  const em = emojiByTrack.get(index);
@@ -6100,6 +6818,77 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6100
6818
  root.appendChild(termsDiv);
6101
6819
  }
6102
6820
  target.appendChild(root);
6821
+ let consentOverlayEl = null;
6822
+ const checkConsentAndShow = () => {
6823
+ try {
6824
+ if (options.skipConsent) return;
6825
+ const unagreed = termsModels.filter((model) => {
6826
+ if (agreedModelsInSession.has(model)) return false;
6827
+ try {
6828
+ if (typeof localStorage === "undefined" || !localStorage) return true;
6829
+ return localStorage.getItem(`dtm_agreed_terms_${model}`) !== "true";
6830
+ } catch (e) {
6831
+ console.warn(
6832
+ "[dtm-player] localStorage access denied in consent check",
6833
+ e
6834
+ );
6835
+ return true;
6836
+ }
6837
+ });
6838
+ if (unagreed.length === 0) return;
6839
+ const consentOverlay = doc.createElement("div");
6840
+ consentOverlay.className = "dtm-consent-overlay";
6841
+ const modal = doc.createElement("div");
6842
+ modal.className = "dtm-win dtm-consent-modal";
6843
+ const header = doc.createElement("div");
6844
+ header.className = "dtm-consent-header";
6845
+ header.textContent = "\u5229\u7528\u898F\u7D04\u306E\u78BA\u8A8D";
6846
+ const body2 = doc.createElement("div");
6847
+ body2.className = "dtm-consent-body";
6848
+ let contentHTML = `<p style="margin: 0 0 8px 0; line-height: 1.4; font-weight: bold; color: var(--dtm-danger);">\u672C\u30C7\u30FC\u30BF\u306B\u306F UTAU \u6B4C\u58F0\u97F3\u6E90\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002<br>\u3054\u5229\u7528\u306B\u3042\u305F\u3063\u3066\u306F\u3001\u4EE5\u4E0B\u306E\u97F3\u6E90\u5229\u7528\u898F\u7D04\u3078\u306E\u540C\u610F\u304C\u5FC5\u8981\u3067\u3059\u3002</p>`;
6849
+ for (const model of unagreed) {
6850
+ const label = KOE_VOICEBANK_LABELS[model] || model;
6851
+ const url2 = KOE_VOICEBANK_TERMS[model];
6852
+ contentHTML += `
6853
+ <div style="margin-bottom: 8px; padding: 6px 10px; background: var(--dtm-deep); border: 2px solid var(--c-black); box-shadow: 2px 2px 0 var(--c-black);">
6854
+ <div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap; font-size: 11px; font-weight: bold; color: var(--dtm-gold);">
6855
+ <span>\u4F7F\u7528\u6642\u306B\u306F</span>
6856
+ <a href="${url2}" target="_blank" rel="noopener noreferrer" style="color: var(--dtm-primary); text-decoration: underline;">${label}UTAU\u97F3\u6E90</a>
6857
+ <span>\u306E\u5229\u7528\u898F\u7D04\u306B\u5F93\u3063\u3066\u304F\u3060\u3055\u3044</span>
6858
+ </div>
6859
+ </div>
6860
+ `;
6861
+ }
6862
+ body2.innerHTML = contentHTML;
6863
+ const footer = doc.createElement("div");
6864
+ footer.className = "dtm-consent-footer";
6865
+ const btn = doc.createElement("button");
6866
+ btn.type = "button";
6867
+ btn.className = "dtm-btn dtm-btn--success";
6868
+ btn.textContent = "\u540C\u610F\u3057\u3066\u5229\u7528\u3059\u308B";
6869
+ btn.onclick = () => {
6870
+ for (const model of unagreed) {
6871
+ try {
6872
+ if (typeof localStorage !== "undefined" && localStorage) {
6873
+ localStorage.setItem(`dtm_agreed_terms_${model}`, "true");
6874
+ }
6875
+ } catch (e) {
6876
+ }
6877
+ agreedModelsInSession.add(model);
6878
+ }
6879
+ consentOverlay.remove();
6880
+ consentOverlayEl = null;
6881
+ };
6882
+ footer.appendChild(btn);
6883
+ modal.append(header, body2, footer);
6884
+ consentOverlay.appendChild(modal);
6885
+ doc.body.appendChild(consentOverlay);
6886
+ consentOverlayEl = consentOverlay;
6887
+ } catch (err2) {
6888
+ console.error("[dtm-player] Error in checkConsentAndShow:", err2);
6889
+ }
6890
+ };
6891
+ checkConsentAndShow();
6103
6892
  const autoScroll = (lane, el) => {
6104
6893
  if (el.offsetWidth === 0 || lane.clientWidth === 0) return;
6105
6894
  const elementCenter = el.offsetLeft + el.offsetWidth / 2;
@@ -6165,7 +6954,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6165
6954
  onTick: (step) => {
6166
6955
  renderPlayhead(step);
6167
6956
  },
6168
- onEnd: () => finish(),
6957
+ onEnd: (interrupted) => finish(),
6169
6958
  stepsPerBar: STEPS_PER_BAR
6170
6959
  });
6171
6960
  let playing = false;
@@ -6260,6 +7049,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6260
7049
  else play();
6261
7050
  });
6262
7051
  const destroy = () => {
7052
+ doc.removeEventListener("click", handleOutsideClick);
6263
7053
  seq.stop();
6264
7054
  peekVoices()?.stopStream();
6265
7055
  if (activePlayer === instance) activePlayer = null;
@@ -6273,6 +7063,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6273
7063
  hideActiveBalloon();
6274
7064
  }
6275
7065
  root.remove();
7066
+ consentOverlayEl?.remove();
7067
+ closeInfoModal();
6276
7068
  };
6277
7069
  const instance = {
6278
7070
  play,
@@ -6314,108 +7106,6 @@ var CHORD_INFO_HTML = `
6314
7106
  </ul>
6315
7107
  </div>
6316
7108
  `;
6317
- var MML_INFO_HTML = `
6318
- <div class="dtm-modal-body-content">
6319
- <h4>1. \u97F3\u7B26\u3068\u4F11\u7B26</h4>
6320
- <p><code>c</code>(\u30C9) <code>d</code>(\u30EC) <code>e</code>(\u30DF) <code>f</code>(\u30D5\u30A1) <code>g</code>(\u30BD) <code>a</code>(\u30E9) <code>b</code>(\u30B7) \u306E\u30A2\u30EB\u30D5\u30A1\u30D9\u30C3\u30C8\u3067\u8868\u3057\u307E\u3059\u3002</p>
6321
- <ul>
6322
- <li>\u534A\u97F3\u4E0A\u3052\u308B: <code>c#</code> \u307E\u305F\u306F <code>c+</code></li>
6323
- <li>\u534A\u97F3\u4E0B\u3052\u308B: <code>d-</code></li>
6324
- <li>\u4F11\u7B26: <code>r</code></li>
6325
- </ul>
6326
-
6327
- <h4>2. \u97F3\u306E\u9577\u3055</h4>
6328
- <p>\u97F3\u540D\u3084\u4F11\u7B26\u306E\u5F8C\u306B\u6570\u5024\u3067\u6307\u5B9A\u3057\u307E\u3059\uFF08\u4F8B: <code>4</code> = 4\u5206\u97F3\u7B26, <code>8</code> = 8\u5206\u97F3\u7B26, <code>16</code> = 16\u5206\u97F3\u7B26\uFF09\u3002</p>
6329
- <ul>
6330
- <li><code>c4</code> : 4\u5206\u97F3\u7B26\u306E\u30C9</li>
6331
- <li><code>r8</code> : 8\u5206\u4F11\u7B26</li>
6332
- <li><code>c4.</code> : \u4ED8\u70B94\u5206\u97F3\u7B26\u306E\u30C9\uFF08\u9577\u3055\u30921.5\u500D\u306B\uFF09</li>
6333
- <li>\u6570\u5024\u3092\u7701\u7565\u3059\u308B\u3068\u3001<code>l</code> \u30B3\u30DE\u30F3\u30C9\u3067\u8A2D\u5B9A\u3055\u308C\u305F\u30C7\u30D5\u30A9\u30EB\u30C8\u9577\uFF08\u901A\u5E3816\u5206\uFF09\u306B\u306A\u308A\u307E\u3059\u3002</li>
6334
- </ul>
6335
-
6336
- <h4>3. \u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u306E\u9AD8\u3055\uFF09</h4>
6337
- <ul>
6338
- <li><code>o4</code>, <code>o5</code> : \u9AD8\u3055\u3092\u76F4\u63A5\u6307\u5B9A\uFF08\u3075\u3064\u3046\u306F o4 \u304B o5\uFF09</li>
6339
- <li><code>&gt;</code> : 1\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u308B</li>
6340
- <li><code>&lt;</code> : 1\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\u3052\u308B</li>
6341
- </ul>
6342
-
6343
- <h4>4. \u30C6\u30F3\u30DD</h4>
6344
- <ul>
6345
- <li><code>t120</code> : \u66F2\u306E\u901F\u3055\u3092BPM120\u306B\u6307\u5B9A\u3002\u203B\u30E1\u30ED\u30C7\u30A3\uFF08@0\uFF09\u306E\u30C6\u30F3\u30DD\u6307\u5B9A\u304C\u66F2\u5168\u4F53\u306B\u53CD\u6620\u3055\u308C\u307E\u3059\u3002</li>
6346
- </ul>
6347
-
6348
- <h4>5. \u548C\u97F3</h4>
6349
- <p>\u97F3\u7B26\u3092 <code>[</code> \u3068 <code>]</code> \u3067\u56F2\u3080\u3068\u540C\u6642\u306B\u767A\u97F3\u3057\u307E\u3059\u3002</p>
6350
- <pre>\u4F8B: [ceg]4 \uFF08\u30C9\u30FB\u30DF\u30FB\u30BD\u30924\u5206\u97F3\u7B26\u3067\u540C\u6642\u306B\u767A\u97F3\uFF09</pre>
6351
-
6352
- <h4>6. \u30C8\u30E9\u30C3\u30AF\u306E\u533A\u5207\u308A</h4>
6353
- <p><code>;</code> \u307E\u305F\u306F <code>@0</code>\u301C<code>@3</code> \u3067\u30C8\u30E9\u30C3\u30AF\u3092\u5207\u308A\u66FF\u3048\u307E\u3059\u3002</p>
6354
- <ul>
6355
- <li><code>@0</code>: \u30E1\u30ED\u30C7\u30A3</li>
6356
- <li><code>@1</code>: \u30B5\u30D6\u30E1\u30ED</li>
6357
- <li><code>@2</code>: \u30D9\u30FC\u30B9</li>
6358
- <li><code>@3</code>: \u4F34\u594F</li>
6359
- </ul>
6360
-
6361
- <h4>7. \u6B4C\u58F0\u30FB\u6B4C\u8A5E\u5165\u529B</h4>
6362
- <p><code>@@&lt;\u30C8\u30E9\u30C3\u30AF\u756A\u53F7&gt; &lt;\u97F3\u6E90\u540D&gt; &lt;\u6B4C\u8A5E&gt;</code> \u306E\u5F62\u5F0F\u3067\u3001\u97F3\u7B26\u3068\u540C\u671F\u3059\u308B\u6B4C\u8A5E\u3092\u5165\u529B\u3067\u304D\u307E\u3059\u3002</p>
6363
- <pre>\u4F8B: @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053</pre>
6364
- <p style="margin-top:4px; margin-bottom:16px;"><small>\uFF08\u97F3\u6E90\u540D\u306F <code>tsukuyomi</code> \u3084 <code>klatt</code>, <code>roze</code> \u306A\u3069\u306E\u97F3\u58F0\u30E2\u30C7\u30EB\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\uFF09</small></p>
6365
-
6366
- <h4 style="margin-top: 18px; border-top: 1px solid var(--dtm-border2); padding-top: 8px;">\u30B5\u30F3\u30D7\u30EB\u66F2\uFF08\u8A66\u8074\u30FB\u30B3\u30D4\u30FC\uFF09</h4>
6367
-
6368
- <!-- \u30B5\u30F3\u30D7\u30EB1 -->
6369
- <div class="dtm-modal-sample-box">
6370
- <div class="dtm-modal-sample-header">
6371
- <span class="dtm-modal-sample-tag">1. \u57FA\u672C\u306E\u30E1\u30ED\u30C7\u30A3</span>
6372
- <button class="dtm-btn dtm-btn--ghost dtm-btn--xs dtm-modal-sample-copy-btn" data-mml="@0 t120 l8 o5 c d e f g a b > c">\u{1F4CB} \u30B3\u30D4\u30FC</button>
6373
- </div>
6374
- <pre style="margin: 0; padding: 6px;">@0 t120 l8 o5 c d e f g a b &gt; c</pre>
6375
- <div class="dtm-modal-sample-desc">
6376
- \u57FA\u672C\u7684\u306A\u30E1\u30ED\u30C7\u30A3\u306E\u66F8\u304D\u65B9\uFF08\u97F3\u540D\u30FB\u9577\u3055\u30FB\u30AA\u30AF\u30BF\u30FC\u30D6\u3068\u30C6\u30F3\u30DD\uFF09\u3002
6377
- </div>
6378
- <div style="margin-top: 8px;">
6379
- <button class="dtm-btn dtm-btn--primary dtm-btn--xs dtm-modal-sample-play-btn" data-mml="@0 t120 l8 o5 c d e f g a b > c">\u25B6 \u8A66\u8074</button>
6380
- </div>
6381
- <div class="dtm-modal-sample-player-container"></div>
6382
- </div>
6383
-
6384
- <!-- \u30B5\u30F3\u30D7\u30EB2 -->
6385
- <div class="dtm-modal-sample-box">
6386
- <div class="dtm-modal-sample-header">
6387
- <span class="dtm-modal-sample-tag">2. \u8907\u6570\u30C8\u30E9\u30C3\u30AF\u3068\u548C\u97F3</span>
6388
- <button class="dtm-btn dtm-btn--ghost dtm-btn--xs dtm-modal-sample-copy-btn" data-mml="@0 t120 o5 c e g2 ; @3 o4 [ceg]2 [ceg]2">\u{1F4CB} \u30B3\u30D4\u30FC</button>
6389
- </div>
6390
- <pre style="margin: 0; padding: 6px;">@0 t120 o5 c e g2 ;
6391
- @3 o4 [ceg]2 [ceg]2</pre>
6392
- <div class="dtm-modal-sample-desc">
6393
- ; \u3067\u30C8\u30E9\u30C3\u30AF\uFF08\u4E0A\uFF1D\u30E1\u30ED\u30C7\u30A3\uFF0F\u4E0B\uFF1D\u4F34\u594F\uFF09\u3092\u5206\u3051\u3001[ceg] \u3067\u548C\u97F3\u3092\u9CF4\u3089\u3057\u307E\u3059\u3002
6394
- </div>
6395
- <div style="margin-top: 8px;">
6396
- <button class="dtm-btn dtm-btn--primary dtm-btn--xs dtm-modal-sample-play-btn" data-mml="@0 t120 o5 c e g2 ; @3 o4 [ceg]2 [ceg]2">\u25B6 \u8A66\u8074</button>
6397
- </div>
6398
- <div class="dtm-modal-sample-player-container"></div>
6399
- </div>
6400
-
6401
- <!-- \u30B5\u30F3\u30D7\u30EB3 -->
6402
- <div class="dtm-modal-sample-box">
6403
- <div class="dtm-modal-sample-header">
6404
- <span class="dtm-modal-sample-tag">3. \u6B4C\u5531\u4ED8\u304D (\u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D)</span>
6405
- <button class="dtm-btn dtm-btn--ghost dtm-btn--xs dtm-modal-sample-copy-btn" data-mml="@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.; @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053;">\u{1F4CB} \u30B3\u30D4\u30FC</button>
6406
- </div>
6407
- <pre style="margin: 0; padding: 6px;">@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.;
6408
- @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053;</pre>
6409
- <div class="dtm-modal-sample-desc">
6410
- @@0 tsukuyomi \u6B4C\u8A5E... \u3067\u30E1\u30ED\u30C7\u30A3\u30C8\u30E9\u30C3\u30AF\u306B\u6B4C\u8A5E\u3092\u540C\u671F\u3055\u305B\u3066\u6B4C\u308F\u305B\u307E\u3059\u3002\u203B\u72EC\u81EA\u62E1\u5F35
6411
- </div>
6412
- <div style="margin-top: 8px;">
6413
- <button class="dtm-btn dtm-btn--primary dtm-btn--xs dtm-modal-sample-play-btn" data-mml="@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.; @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053;">\u25B6 \u8A66\u8074</button>
6414
- </div>
6415
- <div class="dtm-modal-sample-player-container"></div>
6416
- </div>
6417
- </div>
6418
- `;
6419
7109
  var MIDI_INFO_HTML = `
6420
7110
  <div class="dtm-modal-body-content">
6421
7111
  <h4>1. MIDI\u30D5\u30A1\u30A4\u30EB\u3068\u306F</h4>
@@ -7178,6 +7868,10 @@ var mountDAW = (target, options = {}) => {
7178
7868
  0,
7179
7869
  Math.floor(step / snapGridSteps) * snapGridSteps
7180
7870
  );
7871
+ if (playbackState === "paused") {
7872
+ playbackState = "stopped";
7873
+ updateTransport();
7874
+ }
7181
7875
  redrawAll();
7182
7876
  });
7183
7877
  setDrawOffset(currentOffsetX, currentOffsetY);
@@ -7253,9 +7947,14 @@ var mountDAW = (target, options = {}) => {
7253
7947
  }
7254
7948
  redrawAll();
7255
7949
  },
7256
- onEnd: () => {
7257
- playbackState = "stopped";
7258
- currentPlayStep = 0;
7950
+ onEnd: (interrupted) => {
7951
+ if (interrupted) {
7952
+ playbackState = "paused";
7953
+ pausedPlayStep = currentPlayStep;
7954
+ } else {
7955
+ playbackState = "stopped";
7956
+ currentPlayStep = 0;
7957
+ }
7259
7958
  updateTransport();
7260
7959
  redrawAll();
7261
7960
  },
@@ -7345,13 +8044,13 @@ var mountDAW = (target, options = {}) => {
7345
8044
  redrawAll();
7346
8045
  };
7347
8046
  const togglePlay = () => {
7348
- if (playbackState === "playing") stop();
8047
+ if (playbackState === "playing") pause();
7349
8048
  else play();
7350
8049
  };
7351
8050
  const updateTransport = () => {
7352
8051
  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>`;
8052
+ const label = playing ? "\u4E00\u6642\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
8053
+ refs.playBtn.innerHTML = `${icon(playing ? "pause" : "play")}<span>${label}</span>`;
7355
8054
  refs.playBtn.classList.toggle("dtm-play--stop", playing);
7356
8055
  };
7357
8056
  const updateUndoRedo = () => {
@@ -7740,20 +8439,25 @@ var mountDAW = (target, options = {}) => {
7740
8439
  };
7741
8440
  const loadMML = (mml) => {
7742
8441
  if (!mml) return;
8442
+ stop();
7743
8443
  clearAll();
7744
8444
  for (const t of trackStates) t.core.setLoadMode(true);
7745
8445
  const {
7746
8446
  placements,
7747
8447
  bpm: parsedBpm,
7748
8448
  lyrics,
7749
- meta
8449
+ meta,
8450
+ mergedTrackCount
7750
8451
  } = parseMML(mml, {
7751
8452
  stepsPerBar: renderConfig.stepsPerBar,
7752
8453
  collectLyrics: true,
7753
8454
  // このDAWのトラック数を超えるチャンネルはベースへ畳み込む(従来挙動)
7754
8455
  clampTrackCount: trackStates.length
7755
8456
  });
7756
- currentInstrument = meta.instrument ?? "";
8457
+ if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
8458
+ currentInstrument = meta.instrument;
8459
+ options.onInstrumentChange?.(meta.instrument);
8460
+ }
7757
8461
  if (meta.drum && drumPatterns[meta.drum]) {
7758
8462
  currentDrumPattern = meta.drum;
7759
8463
  refs.drumSelect.value = meta.drum;
@@ -7772,8 +8476,8 @@ var mountDAW = (target, options = {}) => {
7772
8476
  t.vocalPan = 64;
7773
8477
  t.vocalOctave = 0;
7774
8478
  }
7775
- lyrics?.forEach((lt, idx) => {
7776
- const t = trackStates[idx];
8479
+ lyrics?.forEach((lt) => {
8480
+ const t = trackStates[lt.trackId];
7777
8481
  if (!t) return;
7778
8482
  t.lyrics = lt.syllables.map((s) => s.kana).join("");
7779
8483
  t.lyricModel = lt.model;
@@ -7805,6 +8509,13 @@ var mountDAW = (target, options = {}) => {
7805
8509
  redrawAll();
7806
8510
  updateTrackPanel();
7807
8511
  updateUndoRedo();
8512
+ if (!isAdvanced && mergedTrackCount > 0) {
8513
+ 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";
8514
+ refs.mmlLoadNote.classList.remove("dtm-hidden");
8515
+ } else {
8516
+ refs.mmlLoadNote.textContent = "";
8517
+ refs.mmlLoadNote.classList.add("dtm-hidden");
8518
+ }
7808
8519
  };
7809
8520
  const applyChord = () => {
7810
8521
  const active = getActive();
@@ -7837,6 +8548,7 @@ var mountDAW = (target, options = {}) => {
7837
8548
  applyMidiSelection(midi, selected);
7838
8549
  };
7839
8550
  const applyMidiSelection = (midi, selectedIndices) => {
8551
+ stop();
7840
8552
  clearAll();
7841
8553
  for (const t of trackStates) t.core.setLoadMode(true);
7842
8554
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -8268,6 +8980,25 @@ var mountDAW = (target, options = {}) => {
8268
8980
  const setLoading = (loading) => {
8269
8981
  refs.topbar.classList.toggle("is-loading", loading);
8270
8982
  };
8983
+ const getCurrentPlayStep = () => {
8984
+ if (playbackState === "playing") return currentPlayStep;
8985
+ if (playbackState === "paused") return pausedPlayStep;
8986
+ return playStartStep;
8987
+ };
8988
+ const forcePauseAt = (step) => {
8989
+ playStartStep = step;
8990
+ pausedPlayStep = step;
8991
+ currentPlayStep = step;
8992
+ playbackState = "paused";
8993
+ const canvas = getGridCanvas();
8994
+ currentOffsetX = Math.max(
8995
+ 0,
8996
+ step * renderConfig.stepWidth - canvas.width * 0.5
8997
+ );
8998
+ setDrawOffset(currentOffsetX, currentOffsetY);
8999
+ updateTransport();
9000
+ redrawAll();
9001
+ };
8271
9002
  return {
8272
9003
  play,
8273
9004
  pause,
@@ -8305,9 +9036,12 @@ var mountDAW = (target, options = {}) => {
8305
9036
  exportMIDI: exportMIDI2,
8306
9037
  setBpm,
8307
9038
  getPlaybackState: () => playbackState,
9039
+ getCurrentPlayStep,
9040
+ forcePauseAt,
8308
9041
  setLoading,
8309
9042
  destroy: () => {
8310
9043
  sequencer.stop();
9044
+ options.singingVoices?.stopStream();
8311
9045
  resizeObserver.disconnect();
8312
9046
  document.removeEventListener("pointermove", onPointerMove);
8313
9047
  document.removeEventListener("pointerup", onPointerUp);
@@ -8317,110 +9051,6 @@ var mountDAW = (target, options = {}) => {
8317
9051
  };
8318
9052
  };
8319
9053
 
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
9054
  // src/headless-player.ts
8425
9055
  var STEPS_PER_BAR2 = 192;
8426
9056
  var playMML = (mml, options = {}) => {
@@ -8471,7 +9101,7 @@ var playMML = (mml, options = {}) => {
8471
9101
  },
8472
9102
  onTick: () => {
8473
9103
  },
8474
- onEnd: () => finish(),
9104
+ onEnd: (interrupted) => finish(),
8475
9105
  stepsPerBar: STEPS_PER_BAR2
8476
9106
  });
8477
9107
  const finish = () => {
@@ -9292,11 +9922,13 @@ var createDtmStudio = async (options = {}) => {
9292
9922
  const mountedPlayers = [];
9293
9923
  const mountedModeSwitches = [];
9294
9924
  const mountEditor = (target, opts = {}) => {
9295
- const { preset, presetUI, ...dawOverrides } = opts;
9925
+ const { preset, presetUI, onInstrumentChange, ...dawOverrides } = opts;
9296
9926
  const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
9297
9927
  const trackIds = tracks.map((t) => t.id);
9298
9928
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
9299
- let editorPreset = presetKey;
9929
+ const meta = opts.initialMML ? parseMmlMeta(opts.initialMML) : {};
9930
+ const initialPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : presetKey;
9931
+ let editorPreset = initialPreset;
9300
9932
  const isAdvancedMode = dawOverrides.mode === "advanced";
9301
9933
  const playNote = (e) => {
9302
9934
  const sf = resolveSoundFont(
@@ -9314,6 +9946,14 @@ var createDtmStudio = async (options = {}) => {
9314
9946
  duration: e.duration
9315
9947
  });
9316
9948
  };
9949
+ let presetSelect = null;
9950
+ const handleInstrumentChange = (key) => {
9951
+ editorPreset = key;
9952
+ if (presetSelect) {
9953
+ presetSelect.setValue(key);
9954
+ }
9955
+ onInstrumentChange?.(key);
9956
+ };
9317
9957
  const base = {
9318
9958
  getAudioTime: () => audioCtx.currentTime,
9319
9959
  onResumeAudio: resumeAudio,
@@ -9322,19 +9962,19 @@ var createDtmStudio = async (options = {}) => {
9322
9962
  singingVoices,
9323
9963
  parseMidi,
9324
9964
  onToggleRecord,
9965
+ onInstrumentChange: handleInstrumentChange,
9325
9966
  ...dawOverrides
9326
9967
  };
9327
9968
  const daw = mountDAW(target, base);
9328
9969
  mountedEditors.push(daw);
9329
9970
  const wantPresetUI = presetUI ?? features.presetUI;
9330
- let presetSelect = null;
9331
9971
  if (wantPresetUI) {
9332
9972
  editorPresetSelects.get(target)?.destroy();
9333
9973
  const rollEl = target.querySelector('[data-dtm="roll"]');
9334
9974
  presetSelect = mountPresetSelect(target, {
9335
9975
  getDaw: () => daw,
9336
9976
  getTrackIds: () => trackIds,
9337
- value: presetKey,
9977
+ value: initialPreset,
9338
9978
  loadingTarget: rollEl ?? target,
9339
9979
  position: "prepend",
9340
9980
  // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
@@ -9344,10 +9984,10 @@ var createDtmStudio = async (options = {}) => {
9344
9984
  });
9345
9985
  editorPresetSelects.set(target, presetSelect);
9346
9986
  }
9347
- daw.setInstrument(presetKey);
9987
+ daw.setInstrument(initialPreset);
9348
9988
  daw.setLoading?.(true);
9349
9989
  void loadPreset(
9350
- presetKey,
9990
+ initialPreset,
9351
9991
  trackIds,
9352
9992
  isAdvancedMode ? "advanced" : "simple"
9353
9993
  ).finally(() => {
@@ -9361,7 +10001,17 @@ var createDtmStudio = async (options = {}) => {
9361
10001
  const i = mountedEditors.indexOf(daw);
9362
10002
  if (i >= 0) mountedEditors.splice(i, 1);
9363
10003
  };
9364
- return { ...daw, destroy };
10004
+ return {
10005
+ ...daw,
10006
+ setInstrument: (name) => {
10007
+ daw.setInstrument(name);
10008
+ editorPreset = name;
10009
+ if (presetSelect) {
10010
+ presetSelect.setValue(name);
10011
+ }
10012
+ },
10013
+ destroy
10014
+ };
9365
10015
  };
9366
10016
  const mountModeSwitch = (target, opts) => {
9367
10017
  const doc = target.ownerDocument;