@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.mjs CHANGED
@@ -1107,6 +1107,9 @@ var ICONS = {
1107
1107
  },
1108
1108
  info: {
1109
1109
  d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
1110
+ },
1111
+ more: {
1112
+ d: "M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
1110
1113
  }
1111
1114
  };
1112
1115
  var icon = (name, size = 20) => {
@@ -1233,6 +1236,7 @@ var buildUI = (target, options) => {
1233
1236
  <textarea class="dtm-textarea dtm-grow" data-dtm="mml-input" placeholder="MML\u3092\u5165\u529B"></textarea>
1234
1237
  <button class="dtm-btn dtm-btn--primary" data-dtm="mml-load">\u8AAD\u8FBC</button>
1235
1238
  </div>
1239
+ <p class="dtm-load-note dtm-hidden" data-dtm="mml-load-note"></p>
1236
1240
  </div>
1237
1241
  </details>
1238
1242
 
@@ -1315,6 +1319,7 @@ var buildUI = (target, options) => {
1315
1319
  <div class="dtm-modal-body" data-dtm="modal-body"></div>
1316
1320
  </div>
1317
1321
  </div>
1322
+
1318
1323
  </div>`;
1319
1324
  const root = q(target, '[data-dtm="root"]');
1320
1325
  const sel = (name) => q(root, `[data-dtm="${name}"]`);
@@ -1358,6 +1363,7 @@ var buildUI = (target, options) => {
1358
1363
  midiPanel: sel("midi-panel"),
1359
1364
  mmlInput: sel("mml-input"),
1360
1365
  mmlLoadBtn: sel("mml-load"),
1366
+ mmlLoadNote: sel("mml-load-note"),
1361
1367
  shiftSelect: sel("shift-select"),
1362
1368
  shiftApplyBtn: sel("shift-apply"),
1363
1369
  macroClear: sel("macro-clear"),
@@ -1511,6 +1517,110 @@ var DRUM_PATTERNS = {
1511
1517
  ]
1512
1518
  };
1513
1519
 
1520
+ // src/instrument-presets.ts
1521
+ var INSTRUMENT_PRESETS = {
1522
+ // --- STANDARD: 汎用性と完成度重視 ---
1523
+ piano: {
1524
+ displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
1525
+ 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",
1526
+ melody: "Acoustic Grand Piano",
1527
+ submelody: "Vibraphone",
1528
+ bass: "Electric Bass (finger)",
1529
+ chord: "Pad 2 (warm)"
1530
+ },
1531
+ acoustic: {
1532
+ displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
1533
+ description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
1534
+ melody: "Acoustic Guitar (steel)",
1535
+ submelody: "Harmonica",
1536
+ bass: "Acoustic Bass",
1537
+ chord: "Acoustic Guitar (nylon)"
1538
+ },
1539
+ jazz_night: {
1540
+ displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
1541
+ 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",
1542
+ melody: "Electric Piano 1",
1543
+ submelody: "Flute",
1544
+ bass: "Acoustic Bass",
1545
+ chord: "Electric Guitar (jazz)"
1546
+ },
1547
+ // --- MODERN & VIBE: エッジの効いた現代的な響き ---
1548
+ synth_pop: {
1549
+ displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
1550
+ description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
1551
+ melody: "Lead 2 (sawtooth)",
1552
+ submelody: "Lead 4 (chiff)",
1553
+ bass: "Synth Bass 2",
1554
+ chord: "Pad 3 (polysynth)"
1555
+ },
1556
+ cyber_punk: {
1557
+ displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
1558
+ 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",
1559
+ melody: "Lead 8 (bass + lead)",
1560
+ submelody: "Lead 5 (charang)",
1561
+ bass: "Synth Bass 2",
1562
+ chord: "Pad 8 (sweep)"
1563
+ },
1564
+ rock: {
1565
+ displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
1566
+ description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
1567
+ melody: "Distortion Guitar",
1568
+ submelody: "Rock Organ",
1569
+ bass: "Electric Bass (pick)",
1570
+ chord: "Overdriven Guitar"
1571
+ },
1572
+ // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
1573
+ orchestra: {
1574
+ displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
1575
+ description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
1576
+ melody: "French Horn",
1577
+ submelody: "Pizzicato Strings",
1578
+ bass: "Cello",
1579
+ chord: "Tremolo Strings"
1580
+ },
1581
+ japanese_wa: {
1582
+ displayName: "\u548C\u98A8\u30FB\u96C5",
1583
+ description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
1584
+ melody: "Koto",
1585
+ submelody: "Shamisen",
1586
+ bass: "Taiko Drum",
1587
+ chord: "Shakuhachi"
1588
+ },
1589
+ arabic_exotic: {
1590
+ displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
1591
+ 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",
1592
+ melody: "Sitar",
1593
+ submelody: "Bagpipe",
1594
+ bass: "Fretless Bass",
1595
+ chord: "Kalimba"
1596
+ },
1597
+ // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
1598
+ fantasy_rpg: {
1599
+ displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
1600
+ description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
1601
+ melody: "Ocarina",
1602
+ submelody: "Celesta",
1603
+ bass: "Timpani",
1604
+ chord: "Orchestral Harp"
1605
+ },
1606
+ ambient_cloud: {
1607
+ displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
1608
+ description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
1609
+ melody: "Lead 6 (voice)",
1610
+ submelody: "Music Box",
1611
+ bass: "Synth Bass 1",
1612
+ chord: "Pad 7 (halo)"
1613
+ },
1614
+ retro_game: {
1615
+ displayName: "8-bit \u30EC\u30C8\u30ED",
1616
+ 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",
1617
+ melody: "Lead 1 (square)",
1618
+ submelody: "Lead 2 (sawtooth)",
1619
+ bass: "Synth Bass 1",
1620
+ chord: "Clavinet"
1621
+ }
1622
+ };
1623
+
1514
1624
  // node_modules/.pnpm/@onjmin+koe@1.0.3/node_modules/@onjmin/koe/dist/index.js
1515
1625
  var MAGIC = 1263486208;
1516
1626
  function parseKoeHeader(headerBytes) {
@@ -2393,7 +2503,20 @@ var createWorkerBackend = async (workerUrl, options) => {
2393
2503
  };
2394
2504
  };
2395
2505
  var createKoeVoice = async (ctx, destination, options) => {
2396
- const backend = options.voiceWorkerUrl ? await createWorkerBackend(options.voiceWorkerUrl, options) : await createLocalBackend(options);
2506
+ let backend;
2507
+ if (options.voiceWorkerUrl) {
2508
+ try {
2509
+ backend = await createWorkerBackend(options.voiceWorkerUrl, options);
2510
+ } catch (err2) {
2511
+ console.warn(
2512
+ "[dtm] Failed to spawn voice worker. Falling back to local backend.",
2513
+ err2
2514
+ );
2515
+ backend = await createLocalBackend(options);
2516
+ }
2517
+ } else {
2518
+ backend = await createLocalBackend(options);
2519
+ }
2397
2520
  const renderCache = /* @__PURE__ */ new Map();
2398
2521
  const inflight = /* @__PURE__ */ new Map();
2399
2522
  const active = /* @__PURE__ */ new Set();
@@ -3908,6 +4031,110 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
3908
4031
  return chordNotes / notes.length >= threshold;
3909
4032
  };
3910
4033
 
4034
+ // src/mml-info.ts
4035
+ var MML_INFO_HTML = `
4036
+ <div class="dtm-modal-body-content">
4037
+ <h4>1. \u97F3\u7B26\u3068\u4F11\u7B26</h4>
4038
+ <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>
4039
+ <ul>
4040
+ <li>\u534A\u97F3\u4E0A\u3052\u308B: <code>c#</code> \u307E\u305F\u306F <code>c+</code></li>
4041
+ <li>\u534A\u97F3\u4E0B\u3052\u308B: <code>d-</code></li>
4042
+ <li>\u4F11\u7B26: <code>r</code></li>
4043
+ </ul>
4044
+
4045
+ <h4>2. \u97F3\u306E\u9577\u3055</h4>
4046
+ <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>
4047
+ <ul>
4048
+ <li><code>c4</code> : 4\u5206\u97F3\u7B26\u306E\u30C9</li>
4049
+ <li><code>r8</code> : 8\u5206\u4F11\u7B26</li>
4050
+ <li><code>c4.</code> : \u4ED8\u70B94\u5206\u97F3\u7B26\u306E\u30C9\uFF08\u9577\u3055\u30921.5\u500D\u306B\uFF09</li>
4051
+ <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>
4052
+ </ul>
4053
+
4054
+ <h4>3. \u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u306E\u9AD8\u3055\uFF09</h4>
4055
+ <ul>
4056
+ <li><code>o4</code>, <code>o5</code> : \u9AD8\u3055\u3092\u76F4\u63A5\u6307\u5B9A\uFF08\u3075\u3064\u3046\u306F o4 \u304B o5\uFF09</li>
4057
+ <li><code>&gt;</code> : 1\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u308B</li>
4058
+ <li><code>&lt;</code> : 1\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\u3052\u308B</li>
4059
+ </ul>
4060
+
4061
+ <h4>4. \u30C6\u30F3\u30DD</h4>
4062
+ <ul>
4063
+ <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>
4064
+ </ul>
4065
+
4066
+ <h4>5. \u548C\u97F3</h4>
4067
+ <p>\u97F3\u7B26\u3092 <code>[</code> \u3068 <code>]</code> \u3067\u56F2\u3080\u3068\u540C\u6642\u306B\u767A\u97F3\u3057\u307E\u3059\u3002</p>
4068
+ <pre>\u4F8B: [ceg]4 \uFF08\u30C9\u30FB\u30DF\u30FB\u30BD\u30924\u5206\u97F3\u7B26\u3067\u540C\u6642\u306B\u767A\u97F3\uFF09</pre>
4069
+
4070
+ <h4>6. \u30C8\u30E9\u30C3\u30AF\u306E\u533A\u5207\u308A</h4>
4071
+ <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>
4072
+ <ul>
4073
+ <li><code>@0</code>: \u30E1\u30ED\u30C7\u30A3</li>
4074
+ <li><code>@1</code>: \u30B5\u30D6\u30E1\u30ED</li>
4075
+ <li><code>@2</code>: \u30D9\u30FC\u30B9</li>
4076
+ <li><code>@3</code>: \u4F34\u594F</li>
4077
+ </ul>
4078
+
4079
+ <h4>7. \u6B4C\u58F0\u30FB\u6B4C\u8A5E\u5165\u529B</h4>
4080
+ <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>
4081
+ <pre>\u4F8B: @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053</pre>
4082
+ <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>
4083
+
4084
+ <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>
4085
+
4086
+ <!-- \u30B5\u30F3\u30D7\u30EB1 -->
4087
+ <div class="dtm-modal-sample-box">
4088
+ <div class="dtm-modal-sample-header">
4089
+ <span class="dtm-modal-sample-tag">1. \u57FA\u672C\u306E\u30E1\u30ED\u30C7\u30A3</span>
4090
+ <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>
4091
+ </div>
4092
+ <pre style="margin: 0; padding: 6px;">@0 t120 l8 o5 c d e f g a b &gt; c</pre>
4093
+ <div class="dtm-modal-sample-desc">
4094
+ \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
4095
+ </div>
4096
+ <div style="margin-top: 8px;">
4097
+ <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>
4098
+ </div>
4099
+ <div class="dtm-modal-sample-player-container"></div>
4100
+ </div>
4101
+
4102
+ <!-- \u30B5\u30F3\u30D7\u30EB2 -->
4103
+ <div class="dtm-modal-sample-box">
4104
+ <div class="dtm-modal-sample-header">
4105
+ <span class="dtm-modal-sample-tag">2. \u8907\u6570\u30C8\u30E9\u30C3\u30AF\u3068\u548C\u97F3</span>
4106
+ <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>
4107
+ </div>
4108
+ <pre style="margin: 0; padding: 6px;">@0 t120 o5 c e g2 ;
4109
+ @3 o4 [ceg]2 [ceg]2</pre>
4110
+ <div class="dtm-modal-sample-desc">
4111
+ ; \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
4112
+ </div>
4113
+ <div style="margin-top: 8px;">
4114
+ <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>
4115
+ </div>
4116
+ <div class="dtm-modal-sample-player-container"></div>
4117
+ </div>
4118
+
4119
+ <!-- \u30B5\u30F3\u30D7\u30EB3 -->
4120
+ <div class="dtm-modal-sample-box">
4121
+ <div class="dtm-modal-sample-header">
4122
+ <span class="dtm-modal-sample-tag">3. \u6B4C\u5531\u4ED8\u304D (\u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D)</span>
4123
+ <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>
4124
+ </div>
4125
+ <pre style="margin: 0; padding: 6px;">@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.;
4126
+ @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053;</pre>
4127
+ <div class="dtm-modal-sample-desc">
4128
+ @@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
4129
+ </div>
4130
+ <div style="margin-top: 8px;">
4131
+ <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>
4132
+ </div>
4133
+ <div class="dtm-modal-sample-player-container"></div>
4134
+ </div>
4135
+ </div>
4136
+ `;
4137
+
3911
4138
  // src/mml-parser.ts
3912
4139
  var PITCH_MAP2 = {
3913
4140
  c: 0,
@@ -3960,6 +4187,7 @@ var parseMML = (mml, options = {}) => {
3960
4187
  bpm,
3961
4188
  tokenTracks: collectTokens ? tokenTracks : void 0,
3962
4189
  lyrics: collectLyrics ? /* @__PURE__ */ new Map() : void 0,
4190
+ mergedTrackCount: 0,
3963
4191
  meta: {}
3964
4192
  };
3965
4193
  }
@@ -3972,13 +4200,24 @@ var parseMML = (mml, options = {}) => {
3972
4200
  const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
3973
4201
  const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
3974
4202
  let trackIndex = 0;
4203
+ let sourceTrackIndex = 0;
3975
4204
  let octave = 4;
3976
4205
  let currentStep = 0;
3977
4206
  let baseLength = 16;
4207
+ const contributors = /* @__PURE__ */ new Map();
4208
+ const recordContributor = () => {
4209
+ let set = contributors.get(trackIndex);
4210
+ if (!set) {
4211
+ set = /* @__PURE__ */ new Set();
4212
+ contributors.set(trackIndex, set);
4213
+ }
4214
+ set.add(sourceTrackIndex);
4215
+ };
3978
4216
  for (const rawPart of parts) {
3979
4217
  const part = rawPart.trim();
3980
4218
  if (part.startsWith("@")) {
3981
4219
  let idx = Number.parseInt(part.substring(1), 10);
4220
+ sourceTrackIndex = idx;
3982
4221
  if (clampTrackCount !== void 0 && idx >= clampTrackCount)
3983
4222
  idx = clampTrackCount - 1;
3984
4223
  trackIndex = idx;
@@ -4102,6 +4341,7 @@ var parseMML = (mml, options = {}) => {
4102
4341
  }
4103
4342
  if (j < body.length && body[j] === "]") j++;
4104
4343
  const steps = parseLength();
4344
+ if (chordNotes.length > 0) recordContributor();
4105
4345
  for (const p of chordNotes) {
4106
4346
  placements.push({
4107
4347
  trackIndex,
@@ -4125,6 +4365,7 @@ var parseMML = (mml, options = {}) => {
4125
4365
  }
4126
4366
  const midiPitch = (octave + 1) * 12 + pitch;
4127
4367
  const steps = parseLength();
4368
+ recordContributor();
4128
4369
  placements.push({
4129
4370
  trackIndex,
4130
4371
  startStep: currentStep,
@@ -4138,11 +4379,16 @@ var parseMML = (mml, options = {}) => {
4138
4379
  }
4139
4380
  }
4140
4381
  }
4382
+ let mergedTrackCount = 0;
4383
+ for (const set of contributors.values()) {
4384
+ if (set.size >= 2) mergedTrackCount++;
4385
+ }
4141
4386
  return {
4142
4387
  placements,
4143
4388
  bpm,
4144
4389
  tokenTracks: collectTokens ? tokenTracks : void 0,
4145
4390
  lyrics,
4391
+ mergedTrackCount,
4146
4392
  meta
4147
4393
  };
4148
4394
  };
@@ -4204,6 +4450,8 @@ var createSequencer = (options) => {
4204
4450
  let active = false;
4205
4451
  let fromStepValue = 0;
4206
4452
  let trackVolumeMap = /* @__PURE__ */ new Map();
4453
+ let lastRealTime = -1;
4454
+ let lastAudioTime = -1;
4207
4455
  let isLooping = false;
4208
4456
  let loopStartStep = 0;
4209
4457
  let loopEndStep = 0;
@@ -4277,6 +4525,21 @@ var createSequencer = (options) => {
4277
4525
  const sps = secondsPerStep();
4278
4526
  const time = options.getAudioTime() - startTime;
4279
4527
  const soloId = options.getSoloTrackId();
4528
+ const nowReal = performance.now() / 1e3;
4529
+ if (lastRealTime > 0 && lastAudioTime >= 0) {
4530
+ const realDelta = nowReal - lastRealTime;
4531
+ const audioDelta = time - lastAudioTime;
4532
+ if (realDelta > 0.5 || audioDelta > 0.5) {
4533
+ console.warn(
4534
+ `[sequencer] Interruption detected (realDelta: ${realDelta.toFixed(3)}s, audioDelta: ${audioDelta.toFixed(3)}s). Stopping playback.`
4535
+ );
4536
+ stop();
4537
+ options.onEnd(true);
4538
+ return;
4539
+ }
4540
+ }
4541
+ lastRealTime = nowReal;
4542
+ lastAudioTime = time;
4280
4543
  for (const track of options.getTracks()) {
4281
4544
  trackVolumeMap.set(track.id, track.volume);
4282
4545
  }
@@ -4353,7 +4616,7 @@ var createSequencer = (options) => {
4353
4616
  const lastDuration = last?.duration ?? 0;
4354
4617
  if (nowIndex >= timeline.length && time > lastWhen + lastDuration + 0.1) {
4355
4618
  stop();
4356
- options.onEnd();
4619
+ options.onEnd(false);
4357
4620
  }
4358
4621
  }
4359
4622
  };
@@ -4394,6 +4657,8 @@ var createSequencer = (options) => {
4394
4657
  }
4395
4658
  loopBase = 0;
4396
4659
  lastPlayStep = fromStepValue - 1e-4;
4660
+ lastRealTime = -1;
4661
+ lastAudioTime = -1;
4397
4662
  intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
4398
4663
  animationId = requestAnimationFrame(animate);
4399
4664
  };
@@ -4492,9 +4757,13 @@ var DAW_CSS = `
4492
4757
  /* \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
4493
4758
  \u30B3\u30F3\u30C8\u30ED\u30FC\u30EB\u30D0\u30FC\uFF08.dtm-controlbar\uFF09\u306B\u3082\u4F9B\u7D66\u3059\u308B\u3002mountPresetSelect /
4494
4759
  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
4495
- 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 */
4760
+ 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
4761
+ \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
4762
+ .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 */
4496
4763
  .dtm-daw,
4497
- .dtm-controlbar {
4764
+ .dtm-controlbar,
4765
+ .dtm-modal-overlay,
4766
+ .dtm-consent-overlay {
4498
4767
  /* PICO-8 16\u8272\u30D1\u30EC\u30C3\u30C8\u3088\u308A */
4499
4768
  --c-black: #000000;
4500
4769
  --c-navy: #1d2b53;
@@ -4591,11 +4860,11 @@ var DAW_CSS = `
4591
4860
  }
4592
4861
  .dtm-btn:active { transform: translate(3px,3px); box-shadow: none; }
4593
4862
  .dtm-btn:disabled { opacity: .3; cursor: default; box-shadow: none; }
4863
+ .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4594
4864
  .dtm-btn--primary { border-color: var(--dtm-primary); background: var(--dtm-primary); color: var(--dtm-pfg); }
4595
4865
  .dtm-btn--success { border-color: var(--dtm-success); background: var(--dtm-success); color: var(--c-black); }
4596
4866
  .dtm-btn--danger { border-color: var(--dtm-danger); background: var(--dtm-danger); color: var(--c-white); }
4597
4867
  .dtm-btn--accent { border-color: var(--dtm-accent); background: var(--dtm-accent); color: var(--c-black); }
4598
- .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4599
4868
  .dtm-btn--icon { padding: 0; }
4600
4869
 
4601
4870
  /* \u2500\u2500\u2500 \u30A2\u30A4\u30B3\u30F3\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
@@ -5092,14 +5361,37 @@ var DAW_CSS = `
5092
5361
  justify-content: center;
5093
5362
  padding: 16px;
5094
5363
  backdrop-filter: blur(2px);
5364
+ /* 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
5365
+ .dtm-daw \u304B\u3089\u7D99\u627F\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u3053\u3053\u3067\u660E\u793A\u3059\u308B\u3002 */
5366
+ color: var(--dtm-text);
5367
+ font-family: var(--dtm-font);
5095
5368
  }
5096
5369
  .dtm-modal-overlay[hidden] {
5097
5370
  display: none !important;
5098
5371
  }
5099
- .dtm-modal {
5100
- max-width: 500px;
5372
+
5373
+ /* \u2500\u2500\u2500 \u5229\u7528\u898F\u7D04\u540C\u610F\u30AB\u30D0\u30FC \u2500\u2500\u2500 */
5374
+ .dtm-consent-overlay {
5375
+ position: fixed;
5376
+ inset: 0;
5377
+ z-index: 10100;
5378
+ background: rgba(0, 0, 0, 0.6);
5379
+ display: flex;
5380
+ align-items: center;
5381
+ justify-content: center;
5382
+ padding: 16px;
5383
+ backdrop-filter: blur(2px);
5384
+ /* 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 */
5385
+ color: var(--dtm-text);
5386
+ font-family: var(--dtm-font);
5387
+ }
5388
+ .dtm-consent-overlay[hidden] {
5389
+ display: none !important;
5390
+ }
5391
+ .dtm-consent-modal {
5392
+ max-width: 450px;
5101
5393
  width: 100%;
5102
- max-height: 80vh;
5394
+ max-height: 85vh;
5103
5395
  display: flex;
5104
5396
  flex-direction: column;
5105
5397
  background: var(--dtm-surface);
@@ -5108,20 +5400,63 @@ var DAW_CSS = `
5108
5400
  inset 0 0 0 2px var(--c-black),
5109
5401
  0 0 0 2px var(--dtm-primary),
5110
5402
  4px 4px 0 var(--c-black);
5111
- overflow: hidden;
5403
+ overflow-y: auto;
5112
5404
  }
5113
- .dtm-modal-header {
5114
- display: flex;
5115
- align-items: center;
5116
- justify-content: space-between;
5405
+ .dtm-consent-header {
5117
5406
  background: var(--dtm-deep);
5407
+ color: var(--dtm-text);
5118
5408
  padding: 8px 12px;
5119
5409
  border-bottom: 2px solid var(--c-black);
5120
- }
5121
- .dtm-modal-title {
5122
- font-family: var(--dtm-font);
5123
- font-size: 14px;
5124
- color: var(--dtm-gold);
5410
+ font-weight: bold;
5411
+ text-align: center;
5412
+ font-size: 14px;
5413
+ }
5414
+ .dtm-consent-body {
5415
+ padding: 12px 16px;
5416
+ font-size: 13px;
5417
+ line-height: 1.6;
5418
+ }
5419
+ .dtm-consent-body a {
5420
+ color: var(--dtm-primary);
5421
+ text-decoration: underline;
5422
+ }
5423
+ .dtm-consent-body a:hover {
5424
+ color: var(--dtm-accent);
5425
+ }
5426
+ .dtm-consent-footer {
5427
+ padding: 8px;
5428
+ border-top: 2px solid var(--c-black);
5429
+ background: var(--dtm-deep);
5430
+ display: flex;
5431
+ justify-content: center;
5432
+ }
5433
+
5434
+ .dtm-modal {
5435
+ max-width: 500px;
5436
+ width: 100%;
5437
+ max-height: 80vh;
5438
+ display: flex;
5439
+ flex-direction: column;
5440
+ background: var(--dtm-surface);
5441
+ border: 2px solid var(--c-black);
5442
+ box-shadow:
5443
+ inset 0 0 0 2px var(--c-black),
5444
+ 0 0 0 2px var(--dtm-primary),
5445
+ 4px 4px 0 var(--c-black);
5446
+ overflow: hidden;
5447
+ }
5448
+ .dtm-modal-header {
5449
+ display: flex;
5450
+ align-items: center;
5451
+ justify-content: space-between;
5452
+ background: var(--dtm-deep);
5453
+ padding: 8px 12px;
5454
+ border-bottom: 2px solid var(--c-black);
5455
+ }
5456
+ .dtm-modal-title {
5457
+ font-family: var(--dtm-font);
5458
+ font-size: 14px;
5459
+ color: var(--dtm-gold);
5125
5460
  font-weight: bold;
5126
5461
  }
5127
5462
  .dtm-modal-close {
@@ -5229,6 +5564,19 @@ var DAW_CSS = `
5229
5564
  }
5230
5565
 
5231
5566
  .dtm-hidden { display: none !important; }
5567
+ /* \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 */
5568
+ .dtm-load-note {
5569
+ margin: 6px 0 0;
5570
+ padding: 0 2px;
5571
+ font-family: var(--dtm-font);
5572
+ font-size: 11px;
5573
+ line-height: 1.5;
5574
+ letter-spacing: .04em;
5575
+ color: var(--dtm-warn); /* \u8B66\u544A\u8272\uFF08\u30AA\u30EC\u30F3\u30B8\uFF09 */
5576
+ font-weight: bold;
5577
+ opacity: 1.0;
5578
+ }
5579
+ .dtm-load-note::before { content: "\u26A0 "; }
5232
5580
  .dtm-grow { flex: 1 1 auto; }
5233
5581
  .dtm-lyric-icon {
5234
5582
  flex: 0 0 auto;
@@ -5328,6 +5676,62 @@ var DAW_CSS = `
5328
5676
  white-space: nowrap;
5329
5677
  }
5330
5678
  .dtm-player-mml-link:hover { color: var(--dtm-primary); }
5679
+ .dtm-player-more-container {
5680
+ position: relative;
5681
+ display: inline-flex;
5682
+ align-items: center;
5683
+ }
5684
+ .dtm-player-more-btn {
5685
+ background: transparent;
5686
+ border: none;
5687
+ color: var(--dtm-muted);
5688
+ cursor: pointer;
5689
+ padding: 2px;
5690
+ display: flex;
5691
+ align-items: center;
5692
+ justify-content: center;
5693
+ border-radius: 50%;
5694
+ width: 24px;
5695
+ height: 24px;
5696
+ transition: color 0.15s, background-color 0.15s;
5697
+ }
5698
+ .dtm-player-more-btn:hover,
5699
+ .dtm-player-more-btn.is-active {
5700
+ color: var(--dtm-text);
5701
+ background: var(--dtm-border2);
5702
+ }
5703
+ .dtm-player-menu {
5704
+ position: absolute;
5705
+ top: calc(100% + 4px);
5706
+ right: 0;
5707
+ background: var(--dtm-deep);
5708
+ border: 2px solid var(--dtm-border2);
5709
+ box-shadow: 4px 4px 0 var(--c-black);
5710
+ z-index: 200;
5711
+ display: flex;
5712
+ flex-direction: column;
5713
+ padding: 4px 0;
5714
+ min-width: 130px;
5715
+ font-family: var(--dtm-font);
5716
+ }
5717
+ .dtm-player-menu-item {
5718
+ background: transparent;
5719
+ border: none;
5720
+ color: var(--dtm-text);
5721
+ padding: 6px 12px;
5722
+ text-align: left;
5723
+ cursor: pointer;
5724
+ font-size: 11px;
5725
+ font-family: inherit;
5726
+ white-space: nowrap;
5727
+ width: 100%;
5728
+ box-sizing: border-box;
5729
+ transition: background-color 0.1s, color 0.1s;
5730
+ }
5731
+ .dtm-player-menu-item:hover {
5732
+ background: var(--dtm-primary);
5733
+ color: var(--dtm-pfg);
5734
+ }
5331
5735
  .dtm-player-emoji {
5332
5736
  position: relative;
5333
5737
  display: inline-flex;
@@ -5573,6 +5977,7 @@ var STEPS_PER_BEAT3 = 48;
5573
5977
  var STEPS_PER_BAR = 192;
5574
5978
  var DEFAULT_TRACK_COLORS = ["#00e436", "#29adff", "#ff77a8", "#ffec27"];
5575
5979
  var activePlayer = null;
5980
+ var agreedModelsInSession = /* @__PURE__ */ new Set();
5576
5981
  var LYRIC_MODEL_LABELS = {
5577
5982
  klatt: "\u8EFD\u91CF\u30ED\u30DC\u58F0",
5578
5983
  ...KOE_VOICEBANK_LABELS
@@ -5603,6 +6008,75 @@ var showBalloon = (balloonEl) => {
5603
6008
  hideActiveBalloon();
5604
6009
  }, 3e3);
5605
6010
  };
6011
+ var copyToClipboard = async (doc, text) => {
6012
+ try {
6013
+ await navigator.clipboard.writeText(text);
6014
+ return true;
6015
+ } catch {
6016
+ try {
6017
+ const textarea = doc.createElement("textarea");
6018
+ textarea.value = text;
6019
+ textarea.style.position = "fixed";
6020
+ textarea.style.opacity = "0";
6021
+ doc.body.appendChild(textarea);
6022
+ textarea.select();
6023
+ const ok = doc.execCommand("copy");
6024
+ doc.body.removeChild(textarea);
6025
+ return ok;
6026
+ } catch {
6027
+ return false;
6028
+ }
6029
+ }
6030
+ };
6031
+ var toBase64Url = (bytes) => {
6032
+ let bin = "";
6033
+ for (let i = 0; i < bytes.length; i++) {
6034
+ bin += String.fromCharCode(bytes[i]);
6035
+ }
6036
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6037
+ };
6038
+ var HIRAGANA_START = 12353;
6039
+ var HIRAGANA_END = 12447;
6040
+ var KATAKANA_START = 12449;
6041
+ var KATAKANA_END = 12543;
6042
+ var PROLONGED_MARK = 12540;
6043
+ var SHIFT_KATAKANA = 255;
6044
+ var VALUE_PROLONGED = 223;
6045
+ var customEncode = (str) => {
6046
+ const bytes = [];
6047
+ for (let i = 0; i < str.length; i++) {
6048
+ const code = str.charCodeAt(i);
6049
+ if (code <= 127) {
6050
+ bytes.push(code);
6051
+ } else if (code === PROLONGED_MARK) {
6052
+ bytes.push(VALUE_PROLONGED);
6053
+ } else if (code >= HIRAGANA_START && code <= HIRAGANA_END) {
6054
+ bytes.push(128 + (code - HIRAGANA_START));
6055
+ } else if (code >= KATAKANA_START && code <= KATAKANA_END) {
6056
+ bytes.push(SHIFT_KATAKANA);
6057
+ bytes.push(128 + (code - 96 - HIRAGANA_START));
6058
+ }
6059
+ }
6060
+ return new Uint8Array(bytes);
6061
+ };
6062
+ var encodeMml = async (mml) => {
6063
+ try {
6064
+ if (typeof CompressionStream !== "undefined") {
6065
+ const cs = new CompressionStream("gzip");
6066
+ const w = cs.writable.getWriter();
6067
+ w.write(customEncode(mml));
6068
+ w.close();
6069
+ const buf = await new Response(cs.readable).arrayBuffer();
6070
+ return `z.${toBase64Url(new Uint8Array(buf))}`;
6071
+ }
6072
+ } catch (e) {
6073
+ console.warn(
6074
+ "[dtm] CompressionStream failed, fallback to encodeURIComponent",
6075
+ e
6076
+ );
6077
+ }
6078
+ return `u.${encodeURIComponent(mml)}`;
6079
+ };
5606
6080
  var mountMmlPlayer = (target, mml, options = {}) => {
5607
6081
  injectStyles(target.ownerDocument ?? document);
5608
6082
  const {
@@ -5748,6 +6222,250 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5748
6222
  emojiEls.push(em);
5749
6223
  emojiByTrack.set(index, em);
5750
6224
  }
6225
+ const menuContainer = doc.createElement("div");
6226
+ menuContainer.className = "dtm-player-more-container";
6227
+ const moreBtn = doc.createElement("button");
6228
+ moreBtn.type = "button";
6229
+ moreBtn.className = "dtm-player-more-btn";
6230
+ moreBtn.innerHTML = icon("more", 14);
6231
+ moreBtn.title = "\u30E1\u30CB\u30E5\u30FC";
6232
+ menuContainer.appendChild(moreBtn);
6233
+ const menuDropdown = doc.createElement("div");
6234
+ menuDropdown.className = "dtm-player-menu";
6235
+ menuDropdown.style.display = "none";
6236
+ const makeMenuItem = (label) => {
6237
+ const item = doc.createElement("button");
6238
+ item.type = "button";
6239
+ item.className = "dtm-player-menu-item";
6240
+ item.textContent = label;
6241
+ return item;
6242
+ };
6243
+ const showMmlItem = makeMenuItem("MML\u3092\u8868\u793A");
6244
+ const mmlInfoItem = makeMenuItem("MML\u66F8\u5F0F\u3068\u306F");
6245
+ const embedItem = makeMenuItem("\u57CB\u3081\u8FBC\u3080");
6246
+ const copyMmlItem = makeMenuItem("MML\u30B3\u30D4\u30FC");
6247
+ menuDropdown.appendChild(showMmlItem);
6248
+ menuDropdown.appendChild(mmlInfoItem);
6249
+ menuDropdown.appendChild(embedItem);
6250
+ menuDropdown.appendChild(copyMmlItem);
6251
+ menuContainer.appendChild(menuDropdown);
6252
+ mmlHeader.appendChild(menuContainer);
6253
+ const toggleMenu = (show) => {
6254
+ const visible = show !== void 0 ? show : menuDropdown.style.display === "none";
6255
+ menuDropdown.style.display = visible ? "flex" : "none";
6256
+ if (visible) {
6257
+ moreBtn.classList.add("is-active");
6258
+ doc.addEventListener("click", handleOutsideClick);
6259
+ } else {
6260
+ moreBtn.classList.remove("is-active");
6261
+ doc.removeEventListener("click", handleOutsideClick);
6262
+ }
6263
+ };
6264
+ const handleOutsideClick = (e) => {
6265
+ if (!menuContainer.contains(e.target)) {
6266
+ toggleMenu(false);
6267
+ }
6268
+ };
6269
+ moreBtn.addEventListener("click", (e) => {
6270
+ e.stopPropagation();
6271
+ toggleMenu();
6272
+ });
6273
+ let infoModalEl = null;
6274
+ let modalSamplePlayer = null;
6275
+ const closeInfoModal = () => {
6276
+ if (modalSamplePlayer) {
6277
+ modalSamplePlayer.stop();
6278
+ modalSamplePlayer.destroy();
6279
+ modalSamplePlayer = null;
6280
+ }
6281
+ infoModalEl?.remove();
6282
+ infoModalEl = null;
6283
+ };
6284
+ const openInfoModal = (title) => {
6285
+ closeInfoModal();
6286
+ const overlay = doc.createElement("div");
6287
+ overlay.className = "dtm-modal-overlay";
6288
+ const modal = doc.createElement("div");
6289
+ modal.className = "dtm-win dtm-modal";
6290
+ const header = doc.createElement("div");
6291
+ header.className = "dtm-modal-header";
6292
+ const titleEl = doc.createElement("span");
6293
+ titleEl.className = "dtm-modal-title";
6294
+ titleEl.textContent = title;
6295
+ const closeBtn = doc.createElement("button");
6296
+ closeBtn.type = "button";
6297
+ closeBtn.className = "dtm-modal-close";
6298
+ closeBtn.innerHTML = "&times;";
6299
+ closeBtn.title = "\u9589\u3058\u308B";
6300
+ header.append(titleEl, closeBtn);
6301
+ const modalBody = doc.createElement("div");
6302
+ modalBody.className = "dtm-modal-body";
6303
+ modal.append(header, modalBody);
6304
+ overlay.appendChild(modal);
6305
+ closeBtn.addEventListener("click", (e) => {
6306
+ e.stopPropagation();
6307
+ closeInfoModal();
6308
+ });
6309
+ overlay.addEventListener("click", (e) => {
6310
+ if (e.target === overlay) closeInfoModal();
6311
+ });
6312
+ doc.body.appendChild(overlay);
6313
+ infoModalEl = overlay;
6314
+ return modalBody;
6315
+ };
6316
+ const appendCopyButton = (parent, text) => {
6317
+ const actions = doc.createElement("div");
6318
+ actions.style.marginTop = "8px";
6319
+ const copyBtn = doc.createElement("button");
6320
+ copyBtn.type = "button";
6321
+ copyBtn.className = "dtm-btn dtm-btn--primary dtm-btn--xs";
6322
+ copyBtn.textContent = "\u{1F4CB} \u30B3\u30D4\u30FC";
6323
+ copyBtn.addEventListener("click", async (e) => {
6324
+ e.stopPropagation();
6325
+ const ok = await copyToClipboard(doc, text);
6326
+ copyBtn.textContent = ok ? "\u2713 \u30B3\u30D4\u30FC\u5B8C\u4E86" : "\u30B3\u30D4\u30FC\u5931\u6557";
6327
+ if (ok) copyBtn.classList.add("dtm-btn--success");
6328
+ setTimeout(() => {
6329
+ copyBtn.textContent = "\u{1F4CB} \u30B3\u30D4\u30FC";
6330
+ copyBtn.classList.remove("dtm-btn--success");
6331
+ }, 1200);
6332
+ });
6333
+ actions.appendChild(copyBtn);
6334
+ parent.appendChild(actions);
6335
+ };
6336
+ const wireSampleButtons = (modalBody) => {
6337
+ const copyBtns = modalBody.querySelectorAll(".dtm-modal-sample-copy-btn");
6338
+ for (const btn of copyBtns) {
6339
+ const el = btn;
6340
+ el.addEventListener("click", async (e) => {
6341
+ e.stopPropagation();
6342
+ const sampleMml = el.getAttribute("data-mml") ?? "";
6343
+ const original = el.textContent;
6344
+ const ok = await copyToClipboard(doc, sampleMml);
6345
+ el.textContent = ok ? "\u2713 \u30B3\u30D4\u30FC\u5B8C\u4E86" : "\u30B3\u30D4\u30FC\u5931\u6557";
6346
+ if (ok) el.classList.add("dtm-btn--success");
6347
+ setTimeout(() => {
6348
+ el.textContent = original;
6349
+ el.classList.remove("dtm-btn--success");
6350
+ }, 1200);
6351
+ });
6352
+ }
6353
+ let activeSampleBtn = null;
6354
+ const resetSampleBtn = (b) => {
6355
+ if (!b) return;
6356
+ b.textContent = "\u25B6 \u8A66\u8074";
6357
+ b.classList.remove("dtm-btn--danger");
6358
+ b.classList.add("dtm-btn--primary");
6359
+ };
6360
+ const markPlaying = (b) => {
6361
+ b.textContent = "\u25A0 \u505C\u6B62";
6362
+ b.classList.remove("dtm-btn--primary");
6363
+ b.classList.add("dtm-btn--danger");
6364
+ };
6365
+ const playBtns = modalBody.querySelectorAll(".dtm-modal-sample-play-btn");
6366
+ for (const btn of playBtns) {
6367
+ const el = btn;
6368
+ el.addEventListener("click", (e) => {
6369
+ e.stopPropagation();
6370
+ const sampleMml = el.getAttribute("data-mml") ?? "";
6371
+ if (activeSampleBtn === el && modalSamplePlayer) {
6372
+ if (modalSamplePlayer.isPlaying()) {
6373
+ modalSamplePlayer.stop();
6374
+ } else {
6375
+ modalSamplePlayer.play();
6376
+ markPlaying(el);
6377
+ }
6378
+ return;
6379
+ }
6380
+ if (modalSamplePlayer) {
6381
+ modalSamplePlayer.stop();
6382
+ modalSamplePlayer.destroy();
6383
+ modalSamplePlayer = null;
6384
+ }
6385
+ resetSampleBtn(activeSampleBtn);
6386
+ activeSampleBtn = el;
6387
+ const sampleBox = el.closest(".dtm-modal-sample-box");
6388
+ const container = sampleBox?.querySelector(
6389
+ ".dtm-modal-sample-player-container"
6390
+ );
6391
+ if (!container) return;
6392
+ container.innerHTML = "";
6393
+ modalSamplePlayer = mountMmlPlayer(container, sampleMml, {
6394
+ onPlayNote: options.onPlayNote,
6395
+ onPlayDrum: options.onPlayDrum,
6396
+ onResumeAudio: options.onResumeAudio,
6397
+ getAudioTime: options.getAudioTime,
6398
+ singingVoices: options.singingVoices,
6399
+ drumPatterns: options.drumPatterns,
6400
+ volume: trackVolume,
6401
+ // 解説モーダル内の試聴サンプルは規約同意を要求しない。
6402
+ skipConsent: true,
6403
+ onStop: () => {
6404
+ if (activeSampleBtn === el) resetSampleBtn(el);
6405
+ }
6406
+ });
6407
+ markPlaying(el);
6408
+ modalSamplePlayer.play();
6409
+ });
6410
+ }
6411
+ };
6412
+ showMmlItem.addEventListener("click", (e) => {
6413
+ e.stopPropagation();
6414
+ toggleMenu(false);
6415
+ const modalBody = openInfoModal("MML");
6416
+ const pre = doc.createElement("pre");
6417
+ pre.textContent = mml;
6418
+ pre.style.whiteSpace = "pre-wrap";
6419
+ pre.style.wordBreak = "break-all";
6420
+ modalBody.appendChild(pre);
6421
+ appendCopyButton(modalBody, mml);
6422
+ });
6423
+ mmlInfoItem.addEventListener("click", (e) => {
6424
+ e.stopPropagation();
6425
+ toggleMenu(false);
6426
+ const modalBody = openInfoModal("MML\u306E\u66F8\u304D\u65B9\u89E3\u8AAC");
6427
+ modalBody.innerHTML = MML_INFO_HTML;
6428
+ wireSampleButtons(modalBody);
6429
+ });
6430
+ embedItem.addEventListener("click", async (e) => {
6431
+ e.stopPropagation();
6432
+ toggleMenu(false);
6433
+ const modalBody = openInfoModal("\u57CB\u3081\u8FBC\u307F");
6434
+ const loading = doc.createElement("p");
6435
+ loading.textContent = "\u751F\u6210\u4E2D...";
6436
+ modalBody.appendChild(loading);
6437
+ try {
6438
+ const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6439
+ const payload = await encodeMml(mml);
6440
+ const url2 = `${embedBase}#${payload}`;
6441
+ const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6442
+ if (!modalBody.isConnected) return;
6443
+ loading.remove();
6444
+ const desc = doc.createElement("p");
6445
+ 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";
6446
+ const pre = doc.createElement("pre");
6447
+ pre.textContent = snippet;
6448
+ pre.style.whiteSpace = "pre-wrap";
6449
+ pre.style.wordBreak = "break-all";
6450
+ modalBody.append(desc, pre);
6451
+ appendCopyButton(modalBody, snippet);
6452
+ } catch (err2) {
6453
+ console.error("[dtm] failed to generate embed snippet", err2);
6454
+ if (modalBody.isConnected) loading.textContent = "\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F";
6455
+ }
6456
+ });
6457
+ copyMmlItem.addEventListener("click", async (e) => {
6458
+ e.stopPropagation();
6459
+ const success = await copyToClipboard(doc, mml);
6460
+ if (success) {
6461
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F\uFF01";
6462
+ } else {
6463
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u5931\u6557";
6464
+ }
6465
+ setTimeout(() => {
6466
+ copyMmlItem.textContent = "MML\u30B3\u30D4\u30FC";
6467
+ }, 2e3);
6468
+ });
5751
6469
  const promotedToImage = /* @__PURE__ */ new Set();
5752
6470
  for (const [index, lt] of lyricTracks) {
5753
6471
  const em = emojiByTrack.get(index);
@@ -5991,6 +6709,77 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5991
6709
  root.appendChild(termsDiv);
5992
6710
  }
5993
6711
  target.appendChild(root);
6712
+ let consentOverlayEl = null;
6713
+ const checkConsentAndShow = () => {
6714
+ try {
6715
+ if (options.skipConsent) return;
6716
+ const unagreed = termsModels.filter((model) => {
6717
+ if (agreedModelsInSession.has(model)) return false;
6718
+ try {
6719
+ if (typeof localStorage === "undefined" || !localStorage) return true;
6720
+ return localStorage.getItem(`dtm_agreed_terms_${model}`) !== "true";
6721
+ } catch (e) {
6722
+ console.warn(
6723
+ "[dtm-player] localStorage access denied in consent check",
6724
+ e
6725
+ );
6726
+ return true;
6727
+ }
6728
+ });
6729
+ if (unagreed.length === 0) return;
6730
+ const consentOverlay = doc.createElement("div");
6731
+ consentOverlay.className = "dtm-consent-overlay";
6732
+ const modal = doc.createElement("div");
6733
+ modal.className = "dtm-win dtm-consent-modal";
6734
+ const header = doc.createElement("div");
6735
+ header.className = "dtm-consent-header";
6736
+ header.textContent = "\u5229\u7528\u898F\u7D04\u306E\u78BA\u8A8D";
6737
+ const body2 = doc.createElement("div");
6738
+ body2.className = "dtm-consent-body";
6739
+ 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>`;
6740
+ for (const model of unagreed) {
6741
+ const label = KOE_VOICEBANK_LABELS[model] || model;
6742
+ const url2 = KOE_VOICEBANK_TERMS[model];
6743
+ contentHTML += `
6744
+ <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);">
6745
+ <div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap; font-size: 11px; font-weight: bold; color: var(--dtm-gold);">
6746
+ <span>\u4F7F\u7528\u6642\u306B\u306F</span>
6747
+ <a href="${url2}" target="_blank" rel="noopener noreferrer" style="color: var(--dtm-primary); text-decoration: underline;">${label}UTAU\u97F3\u6E90</a>
6748
+ <span>\u306E\u5229\u7528\u898F\u7D04\u306B\u5F93\u3063\u3066\u304F\u3060\u3055\u3044</span>
6749
+ </div>
6750
+ </div>
6751
+ `;
6752
+ }
6753
+ body2.innerHTML = contentHTML;
6754
+ const footer = doc.createElement("div");
6755
+ footer.className = "dtm-consent-footer";
6756
+ const btn = doc.createElement("button");
6757
+ btn.type = "button";
6758
+ btn.className = "dtm-btn dtm-btn--success";
6759
+ btn.textContent = "\u540C\u610F\u3057\u3066\u5229\u7528\u3059\u308B";
6760
+ btn.onclick = () => {
6761
+ for (const model of unagreed) {
6762
+ try {
6763
+ if (typeof localStorage !== "undefined" && localStorage) {
6764
+ localStorage.setItem(`dtm_agreed_terms_${model}`, "true");
6765
+ }
6766
+ } catch (e) {
6767
+ }
6768
+ agreedModelsInSession.add(model);
6769
+ }
6770
+ consentOverlay.remove();
6771
+ consentOverlayEl = null;
6772
+ };
6773
+ footer.appendChild(btn);
6774
+ modal.append(header, body2, footer);
6775
+ consentOverlay.appendChild(modal);
6776
+ doc.body.appendChild(consentOverlay);
6777
+ consentOverlayEl = consentOverlay;
6778
+ } catch (err2) {
6779
+ console.error("[dtm-player] Error in checkConsentAndShow:", err2);
6780
+ }
6781
+ };
6782
+ checkConsentAndShow();
5994
6783
  const autoScroll = (lane, el) => {
5995
6784
  if (el.offsetWidth === 0 || lane.clientWidth === 0) return;
5996
6785
  const elementCenter = el.offsetLeft + el.offsetWidth / 2;
@@ -6056,7 +6845,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6056
6845
  onTick: (step) => {
6057
6846
  renderPlayhead(step);
6058
6847
  },
6059
- onEnd: () => finish(),
6848
+ onEnd: (interrupted) => finish(),
6060
6849
  stepsPerBar: STEPS_PER_BAR
6061
6850
  });
6062
6851
  let playing = false;
@@ -6151,6 +6940,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6151
6940
  else play();
6152
6941
  });
6153
6942
  const destroy = () => {
6943
+ doc.removeEventListener("click", handleOutsideClick);
6154
6944
  seq.stop();
6155
6945
  peekVoices()?.stopStream();
6156
6946
  if (activePlayer === instance) activePlayer = null;
@@ -6164,6 +6954,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6164
6954
  hideActiveBalloon();
6165
6955
  }
6166
6956
  root.remove();
6957
+ consentOverlayEl?.remove();
6958
+ closeInfoModal();
6167
6959
  };
6168
6960
  const instance = {
6169
6961
  play,
@@ -6205,108 +6997,6 @@ var CHORD_INFO_HTML = `
6205
6997
  </ul>
6206
6998
  </div>
6207
6999
  `;
6208
- var MML_INFO_HTML = `
6209
- <div class="dtm-modal-body-content">
6210
- <h4>1. \u97F3\u7B26\u3068\u4F11\u7B26</h4>
6211
- <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>
6212
- <ul>
6213
- <li>\u534A\u97F3\u4E0A\u3052\u308B: <code>c#</code> \u307E\u305F\u306F <code>c+</code></li>
6214
- <li>\u534A\u97F3\u4E0B\u3052\u308B: <code>d-</code></li>
6215
- <li>\u4F11\u7B26: <code>r</code></li>
6216
- </ul>
6217
-
6218
- <h4>2. \u97F3\u306E\u9577\u3055</h4>
6219
- <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>
6220
- <ul>
6221
- <li><code>c4</code> : 4\u5206\u97F3\u7B26\u306E\u30C9</li>
6222
- <li><code>r8</code> : 8\u5206\u4F11\u7B26</li>
6223
- <li><code>c4.</code> : \u4ED8\u70B94\u5206\u97F3\u7B26\u306E\u30C9\uFF08\u9577\u3055\u30921.5\u500D\u306B\uFF09</li>
6224
- <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>
6225
- </ul>
6226
-
6227
- <h4>3. \u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u306E\u9AD8\u3055\uFF09</h4>
6228
- <ul>
6229
- <li><code>o4</code>, <code>o5</code> : \u9AD8\u3055\u3092\u76F4\u63A5\u6307\u5B9A\uFF08\u3075\u3064\u3046\u306F o4 \u304B o5\uFF09</li>
6230
- <li><code>&gt;</code> : 1\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u308B</li>
6231
- <li><code>&lt;</code> : 1\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\u3052\u308B</li>
6232
- </ul>
6233
-
6234
- <h4>4. \u30C6\u30F3\u30DD</h4>
6235
- <ul>
6236
- <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>
6237
- </ul>
6238
-
6239
- <h4>5. \u548C\u97F3</h4>
6240
- <p>\u97F3\u7B26\u3092 <code>[</code> \u3068 <code>]</code> \u3067\u56F2\u3080\u3068\u540C\u6642\u306B\u767A\u97F3\u3057\u307E\u3059\u3002</p>
6241
- <pre>\u4F8B: [ceg]4 \uFF08\u30C9\u30FB\u30DF\u30FB\u30BD\u30924\u5206\u97F3\u7B26\u3067\u540C\u6642\u306B\u767A\u97F3\uFF09</pre>
6242
-
6243
- <h4>6. \u30C8\u30E9\u30C3\u30AF\u306E\u533A\u5207\u308A</h4>
6244
- <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>
6245
- <ul>
6246
- <li><code>@0</code>: \u30E1\u30ED\u30C7\u30A3</li>
6247
- <li><code>@1</code>: \u30B5\u30D6\u30E1\u30ED</li>
6248
- <li><code>@2</code>: \u30D9\u30FC\u30B9</li>
6249
- <li><code>@3</code>: \u4F34\u594F</li>
6250
- </ul>
6251
-
6252
- <h4>7. \u6B4C\u58F0\u30FB\u6B4C\u8A5E\u5165\u529B</h4>
6253
- <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>
6254
- <pre>\u4F8B: @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053</pre>
6255
- <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>
6256
-
6257
- <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>
6258
-
6259
- <!-- \u30B5\u30F3\u30D7\u30EB1 -->
6260
- <div class="dtm-modal-sample-box">
6261
- <div class="dtm-modal-sample-header">
6262
- <span class="dtm-modal-sample-tag">1. \u57FA\u672C\u306E\u30E1\u30ED\u30C7\u30A3</span>
6263
- <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>
6264
- </div>
6265
- <pre style="margin: 0; padding: 6px;">@0 t120 l8 o5 c d e f g a b &gt; c</pre>
6266
- <div class="dtm-modal-sample-desc">
6267
- \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
6268
- </div>
6269
- <div style="margin-top: 8px;">
6270
- <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>
6271
- </div>
6272
- <div class="dtm-modal-sample-player-container"></div>
6273
- </div>
6274
-
6275
- <!-- \u30B5\u30F3\u30D7\u30EB2 -->
6276
- <div class="dtm-modal-sample-box">
6277
- <div class="dtm-modal-sample-header">
6278
- <span class="dtm-modal-sample-tag">2. \u8907\u6570\u30C8\u30E9\u30C3\u30AF\u3068\u548C\u97F3</span>
6279
- <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>
6280
- </div>
6281
- <pre style="margin: 0; padding: 6px;">@0 t120 o5 c e g2 ;
6282
- @3 o4 [ceg]2 [ceg]2</pre>
6283
- <div class="dtm-modal-sample-desc">
6284
- ; \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
6285
- </div>
6286
- <div style="margin-top: 8px;">
6287
- <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>
6288
- </div>
6289
- <div class="dtm-modal-sample-player-container"></div>
6290
- </div>
6291
-
6292
- <!-- \u30B5\u30F3\u30D7\u30EB3 -->
6293
- <div class="dtm-modal-sample-box">
6294
- <div class="dtm-modal-sample-header">
6295
- <span class="dtm-modal-sample-tag">3. \u6B4C\u5531\u4ED8\u304D (\u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D)</span>
6296
- <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>
6297
- </div>
6298
- <pre style="margin: 0; padding: 6px;">@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.;
6299
- @@0 tsukuyomi \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u3069\u3093\u3050\u308A\u3053;</pre>
6300
- <div class="dtm-modal-sample-desc">
6301
- @@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
6302
- </div>
6303
- <div style="margin-top: 8px;">
6304
- <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>
6305
- </div>
6306
- <div class="dtm-modal-sample-player-container"></div>
6307
- </div>
6308
- </div>
6309
- `;
6310
7000
  var MIDI_INFO_HTML = `
6311
7001
  <div class="dtm-modal-body-content">
6312
7002
  <h4>1. MIDI\u30D5\u30A1\u30A4\u30EB\u3068\u306F</h4>
@@ -7069,6 +7759,10 @@ var mountDAW = (target, options = {}) => {
7069
7759
  0,
7070
7760
  Math.floor(step / snapGridSteps) * snapGridSteps
7071
7761
  );
7762
+ if (playbackState === "paused") {
7763
+ playbackState = "stopped";
7764
+ updateTransport();
7765
+ }
7072
7766
  redrawAll();
7073
7767
  });
7074
7768
  setDrawOffset(currentOffsetX, currentOffsetY);
@@ -7144,9 +7838,14 @@ var mountDAW = (target, options = {}) => {
7144
7838
  }
7145
7839
  redrawAll();
7146
7840
  },
7147
- onEnd: () => {
7148
- playbackState = "stopped";
7149
- currentPlayStep = 0;
7841
+ onEnd: (interrupted) => {
7842
+ if (interrupted) {
7843
+ playbackState = "paused";
7844
+ pausedPlayStep = currentPlayStep;
7845
+ } else {
7846
+ playbackState = "stopped";
7847
+ currentPlayStep = 0;
7848
+ }
7150
7849
  updateTransport();
7151
7850
  redrawAll();
7152
7851
  },
@@ -7236,13 +7935,13 @@ var mountDAW = (target, options = {}) => {
7236
7935
  redrawAll();
7237
7936
  };
7238
7937
  const togglePlay = () => {
7239
- if (playbackState === "playing") stop();
7938
+ if (playbackState === "playing") pause();
7240
7939
  else play();
7241
7940
  };
7242
7941
  const updateTransport = () => {
7243
7942
  const playing = playbackState === "playing";
7244
- const label = playing ? "\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7245
- refs.playBtn.innerHTML = `${icon(playing ? "stop" : "play")}<span>${label}</span>`;
7943
+ const label = playing ? "\u4E00\u6642\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7944
+ refs.playBtn.innerHTML = `${icon(playing ? "pause" : "play")}<span>${label}</span>`;
7246
7945
  refs.playBtn.classList.toggle("dtm-play--stop", playing);
7247
7946
  };
7248
7947
  const updateUndoRedo = () => {
@@ -7631,20 +8330,25 @@ var mountDAW = (target, options = {}) => {
7631
8330
  };
7632
8331
  const loadMML = (mml) => {
7633
8332
  if (!mml) return;
8333
+ stop();
7634
8334
  clearAll();
7635
8335
  for (const t of trackStates) t.core.setLoadMode(true);
7636
8336
  const {
7637
8337
  placements,
7638
8338
  bpm: parsedBpm,
7639
8339
  lyrics,
7640
- meta
8340
+ meta,
8341
+ mergedTrackCount
7641
8342
  } = parseMML(mml, {
7642
8343
  stepsPerBar: renderConfig.stepsPerBar,
7643
8344
  collectLyrics: true,
7644
8345
  // このDAWのトラック数を超えるチャンネルはベースへ畳み込む(従来挙動)
7645
8346
  clampTrackCount: trackStates.length
7646
8347
  });
7647
- currentInstrument = meta.instrument ?? "";
8348
+ if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
8349
+ currentInstrument = meta.instrument;
8350
+ options.onInstrumentChange?.(meta.instrument);
8351
+ }
7648
8352
  if (meta.drum && drumPatterns[meta.drum]) {
7649
8353
  currentDrumPattern = meta.drum;
7650
8354
  refs.drumSelect.value = meta.drum;
@@ -7663,8 +8367,8 @@ var mountDAW = (target, options = {}) => {
7663
8367
  t.vocalPan = 64;
7664
8368
  t.vocalOctave = 0;
7665
8369
  }
7666
- lyrics?.forEach((lt, idx) => {
7667
- const t = trackStates[idx];
8370
+ lyrics?.forEach((lt) => {
8371
+ const t = trackStates[lt.trackId];
7668
8372
  if (!t) return;
7669
8373
  t.lyrics = lt.syllables.map((s) => s.kana).join("");
7670
8374
  t.lyricModel = lt.model;
@@ -7696,6 +8400,13 @@ var mountDAW = (target, options = {}) => {
7696
8400
  redrawAll();
7697
8401
  updateTrackPanel();
7698
8402
  updateUndoRedo();
8403
+ if (!isAdvanced && mergedTrackCount > 0) {
8404
+ 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";
8405
+ refs.mmlLoadNote.classList.remove("dtm-hidden");
8406
+ } else {
8407
+ refs.mmlLoadNote.textContent = "";
8408
+ refs.mmlLoadNote.classList.add("dtm-hidden");
8409
+ }
7699
8410
  };
7700
8411
  const applyChord = () => {
7701
8412
  const active = getActive();
@@ -7728,6 +8439,7 @@ var mountDAW = (target, options = {}) => {
7728
8439
  applyMidiSelection(midi, selected);
7729
8440
  };
7730
8441
  const applyMidiSelection = (midi, selectedIndices) => {
8442
+ stop();
7731
8443
  clearAll();
7732
8444
  for (const t of trackStates) t.core.setLoadMode(true);
7733
8445
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -8159,6 +8871,25 @@ var mountDAW = (target, options = {}) => {
8159
8871
  const setLoading = (loading) => {
8160
8872
  refs.topbar.classList.toggle("is-loading", loading);
8161
8873
  };
8874
+ const getCurrentPlayStep = () => {
8875
+ if (playbackState === "playing") return currentPlayStep;
8876
+ if (playbackState === "paused") return pausedPlayStep;
8877
+ return playStartStep;
8878
+ };
8879
+ const forcePauseAt = (step) => {
8880
+ playStartStep = step;
8881
+ pausedPlayStep = step;
8882
+ currentPlayStep = step;
8883
+ playbackState = "paused";
8884
+ const canvas = getGridCanvas();
8885
+ currentOffsetX = Math.max(
8886
+ 0,
8887
+ step * renderConfig.stepWidth - canvas.width * 0.5
8888
+ );
8889
+ setDrawOffset(currentOffsetX, currentOffsetY);
8890
+ updateTransport();
8891
+ redrawAll();
8892
+ };
8162
8893
  return {
8163
8894
  play,
8164
8895
  pause,
@@ -8196,9 +8927,12 @@ var mountDAW = (target, options = {}) => {
8196
8927
  exportMIDI: exportMIDI2,
8197
8928
  setBpm,
8198
8929
  getPlaybackState: () => playbackState,
8930
+ getCurrentPlayStep,
8931
+ forcePauseAt,
8199
8932
  setLoading,
8200
8933
  destroy: () => {
8201
8934
  sequencer.stop();
8935
+ options.singingVoices?.stopStream();
8202
8936
  resizeObserver.disconnect();
8203
8937
  document.removeEventListener("pointermove", onPointerMove);
8204
8938
  document.removeEventListener("pointerup", onPointerUp);
@@ -8208,110 +8942,6 @@ var mountDAW = (target, options = {}) => {
8208
8942
  };
8209
8943
  };
8210
8944
 
8211
- // src/instrument-presets.ts
8212
- var INSTRUMENT_PRESETS = {
8213
- // --- STANDARD: 汎用性と完成度重視 ---
8214
- piano: {
8215
- displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
8216
- 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",
8217
- melody: "Acoustic Grand Piano",
8218
- submelody: "Vibraphone",
8219
- bass: "Electric Bass (finger)",
8220
- chord: "Pad 2 (warm)"
8221
- },
8222
- acoustic: {
8223
- displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
8224
- description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
8225
- melody: "Acoustic Guitar (steel)",
8226
- submelody: "Harmonica",
8227
- bass: "Acoustic Bass",
8228
- chord: "Acoustic Guitar (nylon)"
8229
- },
8230
- jazz_night: {
8231
- displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
8232
- 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",
8233
- melody: "Electric Piano 1",
8234
- submelody: "Flute",
8235
- bass: "Acoustic Bass",
8236
- chord: "Electric Guitar (jazz)"
8237
- },
8238
- // --- MODERN & VIBE: エッジの効いた現代的な響き ---
8239
- synth_pop: {
8240
- displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
8241
- description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
8242
- melody: "Lead 2 (sawtooth)",
8243
- submelody: "Lead 4 (chiff)",
8244
- bass: "Synth Bass 2",
8245
- chord: "Pad 3 (polysynth)"
8246
- },
8247
- cyber_punk: {
8248
- displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
8249
- 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",
8250
- melody: "Lead 8 (bass + lead)",
8251
- submelody: "Lead 5 (charang)",
8252
- bass: "Synth Bass 2",
8253
- chord: "Pad 8 (sweep)"
8254
- },
8255
- rock: {
8256
- displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
8257
- description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
8258
- melody: "Distortion Guitar",
8259
- submelody: "Rock Organ",
8260
- bass: "Electric Bass (pick)",
8261
- chord: "Overdriven Guitar"
8262
- },
8263
- // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
8264
- orchestra: {
8265
- displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
8266
- description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
8267
- melody: "French Horn",
8268
- submelody: "Pizzicato Strings",
8269
- bass: "Cello",
8270
- chord: "Tremolo Strings"
8271
- },
8272
- japanese_wa: {
8273
- displayName: "\u548C\u98A8\u30FB\u96C5",
8274
- description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
8275
- melody: "Koto",
8276
- submelody: "Shamisen",
8277
- bass: "Taiko Drum",
8278
- chord: "Shakuhachi"
8279
- },
8280
- arabic_exotic: {
8281
- displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
8282
- 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",
8283
- melody: "Sitar",
8284
- submelody: "Bagpipe",
8285
- bass: "Fretless Bass",
8286
- chord: "Kalimba"
8287
- },
8288
- // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
8289
- fantasy_rpg: {
8290
- displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
8291
- description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
8292
- melody: "Ocarina",
8293
- submelody: "Celesta",
8294
- bass: "Timpani",
8295
- chord: "Orchestral Harp"
8296
- },
8297
- ambient_cloud: {
8298
- displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
8299
- description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
8300
- melody: "Lead 6 (voice)",
8301
- submelody: "Music Box",
8302
- bass: "Synth Bass 1",
8303
- chord: "Pad 7 (halo)"
8304
- },
8305
- retro_game: {
8306
- displayName: "8-bit \u30EC\u30C8\u30ED",
8307
- 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",
8308
- melody: "Lead 1 (square)",
8309
- submelody: "Lead 2 (sawtooth)",
8310
- bass: "Synth Bass 1",
8311
- chord: "Clavinet"
8312
- }
8313
- };
8314
-
8315
8945
  // src/headless-player.ts
8316
8946
  var STEPS_PER_BAR2 = 192;
8317
8947
  var playMML = (mml, options = {}) => {
@@ -8362,7 +8992,7 @@ var playMML = (mml, options = {}) => {
8362
8992
  },
8363
8993
  onTick: () => {
8364
8994
  },
8365
- onEnd: () => finish(),
8995
+ onEnd: (interrupted) => finish(),
8366
8996
  stepsPerBar: STEPS_PER_BAR2
8367
8997
  });
8368
8998
  const finish = () => {
@@ -9182,11 +9812,13 @@ var createDtmStudio = async (options = {}) => {
9182
9812
  const mountedPlayers = [];
9183
9813
  const mountedModeSwitches = [];
9184
9814
  const mountEditor = (target, opts = {}) => {
9185
- const { preset, presetUI, ...dawOverrides } = opts;
9815
+ const { preset, presetUI, onInstrumentChange, ...dawOverrides } = opts;
9186
9816
  const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
9187
9817
  const trackIds = tracks.map((t) => t.id);
9188
9818
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
9189
- let editorPreset = presetKey;
9819
+ const meta = opts.initialMML ? parseMmlMeta(opts.initialMML) : {};
9820
+ const initialPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : presetKey;
9821
+ let editorPreset = initialPreset;
9190
9822
  const isAdvancedMode = dawOverrides.mode === "advanced";
9191
9823
  const playNote = (e) => {
9192
9824
  const sf = resolveSoundFont(
@@ -9204,6 +9836,14 @@ var createDtmStudio = async (options = {}) => {
9204
9836
  duration: e.duration
9205
9837
  });
9206
9838
  };
9839
+ let presetSelect = null;
9840
+ const handleInstrumentChange = (key) => {
9841
+ editorPreset = key;
9842
+ if (presetSelect) {
9843
+ presetSelect.setValue(key);
9844
+ }
9845
+ onInstrumentChange?.(key);
9846
+ };
9207
9847
  const base = {
9208
9848
  getAudioTime: () => audioCtx.currentTime,
9209
9849
  onResumeAudio: resumeAudio,
@@ -9212,19 +9852,19 @@ var createDtmStudio = async (options = {}) => {
9212
9852
  singingVoices,
9213
9853
  parseMidi,
9214
9854
  onToggleRecord,
9855
+ onInstrumentChange: handleInstrumentChange,
9215
9856
  ...dawOverrides
9216
9857
  };
9217
9858
  const daw = mountDAW(target, base);
9218
9859
  mountedEditors.push(daw);
9219
9860
  const wantPresetUI = presetUI ?? features.presetUI;
9220
- let presetSelect = null;
9221
9861
  if (wantPresetUI) {
9222
9862
  editorPresetSelects.get(target)?.destroy();
9223
9863
  const rollEl = target.querySelector('[data-dtm="roll"]');
9224
9864
  presetSelect = mountPresetSelect(target, {
9225
9865
  getDaw: () => daw,
9226
9866
  getTrackIds: () => trackIds,
9227
- value: presetKey,
9867
+ value: initialPreset,
9228
9868
  loadingTarget: rollEl ?? target,
9229
9869
  position: "prepend",
9230
9870
  // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
@@ -9234,10 +9874,10 @@ var createDtmStudio = async (options = {}) => {
9234
9874
  });
9235
9875
  editorPresetSelects.set(target, presetSelect);
9236
9876
  }
9237
- daw.setInstrument(presetKey);
9877
+ daw.setInstrument(initialPreset);
9238
9878
  daw.setLoading?.(true);
9239
9879
  void loadPreset(
9240
- presetKey,
9880
+ initialPreset,
9241
9881
  trackIds,
9242
9882
  isAdvancedMode ? "advanced" : "simple"
9243
9883
  ).finally(() => {
@@ -9251,7 +9891,17 @@ var createDtmStudio = async (options = {}) => {
9251
9891
  const i = mountedEditors.indexOf(daw);
9252
9892
  if (i >= 0) mountedEditors.splice(i, 1);
9253
9893
  };
9254
- return { ...daw, destroy };
9894
+ return {
9895
+ ...daw,
9896
+ setInstrument: (name) => {
9897
+ daw.setInstrument(name);
9898
+ editorPreset = name;
9899
+ if (presetSelect) {
9900
+ presetSelect.setValue(name);
9901
+ }
9902
+ },
9903
+ destroy
9904
+ };
9255
9905
  };
9256
9906
  const mountModeSwitch = (target, opts) => {
9257
9907
  const doc = target.ownerDocument;