@onjmin/dtm 0.1.23 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1107,6 +1107,9 @@ var ICONS = {
1107
1107
  },
1108
1108
  info: {
1109
1109
  d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
1110
+ },
1111
+ more: {
1112
+ d: "M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
1110
1113
  }
1111
1114
  };
1112
1115
  var icon = (name, size = 20) => {
@@ -1233,6 +1236,7 @@ var buildUI = (target, options) => {
1233
1236
  <textarea class="dtm-textarea dtm-grow" data-dtm="mml-input" placeholder="MML\u3092\u5165\u529B"></textarea>
1234
1237
  <button class="dtm-btn dtm-btn--primary" data-dtm="mml-load">\u8AAD\u8FBC</button>
1235
1238
  </div>
1239
+ <p class="dtm-load-note dtm-hidden" data-dtm="mml-load-note"></p>
1236
1240
  </div>
1237
1241
  </details>
1238
1242
 
@@ -1358,6 +1362,7 @@ var buildUI = (target, options) => {
1358
1362
  midiPanel: sel("midi-panel"),
1359
1363
  mmlInput: sel("mml-input"),
1360
1364
  mmlLoadBtn: sel("mml-load"),
1365
+ mmlLoadNote: sel("mml-load-note"),
1361
1366
  shiftSelect: sel("shift-select"),
1362
1367
  shiftApplyBtn: sel("shift-apply"),
1363
1368
  macroClear: sel("macro-clear"),
@@ -1511,6 +1516,110 @@ var DRUM_PATTERNS = {
1511
1516
  ]
1512
1517
  };
1513
1518
 
1519
+ // src/instrument-presets.ts
1520
+ var INSTRUMENT_PRESETS = {
1521
+ // --- STANDARD: 汎用性と完成度重視 ---
1522
+ piano: {
1523
+ displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
1524
+ description: "\u6700\u3082\u7834\u7DBB\u3057\u306B\u304F\u3044\u69CB\u6210\u3002\u697D\u66F2\u5236\u4F5C\u306E\u30B9\u30B1\u30C3\u30C1\u306B\u3082\u6700\u9069\u3002",
1525
+ melody: "Acoustic Grand Piano",
1526
+ submelody: "Vibraphone",
1527
+ bass: "Electric Bass (finger)",
1528
+ chord: "Pad 2 (warm)"
1529
+ },
1530
+ acoustic: {
1531
+ displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
1532
+ description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
1533
+ melody: "Acoustic Guitar (steel)",
1534
+ submelody: "Harmonica",
1535
+ bass: "Acoustic Bass",
1536
+ chord: "Acoustic Guitar (nylon)"
1537
+ },
1538
+ jazz_night: {
1539
+ displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
1540
+ description: "Rhodes\u98A8\u306EEP\u3068\u30A6\u30C3\u30C9\u30D9\u30FC\u30B9\u306B\u3088\u308B\u3001\u5927\u4EBA\u3073\u305F\u30A2\u30F3\u30B5\u30F3\u30D6\u30EB\u3002",
1541
+ melody: "Electric Piano 1",
1542
+ submelody: "Flute",
1543
+ bass: "Acoustic Bass",
1544
+ chord: "Electric Guitar (jazz)"
1545
+ },
1546
+ // --- MODERN & VIBE: エッジの効いた現代的な響き ---
1547
+ synth_pop: {
1548
+ displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
1549
+ description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
1550
+ melody: "Lead 2 (sawtooth)",
1551
+ submelody: "Lead 4 (chiff)",
1552
+ bass: "Synth Bass 2",
1553
+ chord: "Pad 3 (polysynth)"
1554
+ },
1555
+ cyber_punk: {
1556
+ displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
1557
+ description: "\u30C7\u30B8\u30BF\u30EB\u306A\u51B7\u305F\u3055\u3068\u6B6A\u307F\u304C\u6DF7\u3056\u308A\u5408\u3046\u3001\u672A\u6765\u7684\u306A\u97FF\u304D\u3002",
1558
+ melody: "Lead 8 (bass + lead)",
1559
+ submelody: "Lead 5 (charang)",
1560
+ bass: "Synth Bass 2",
1561
+ chord: "Pad 8 (sweep)"
1562
+ },
1563
+ rock: {
1564
+ displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
1565
+ description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
1566
+ melody: "Distortion Guitar",
1567
+ submelody: "Rock Organ",
1568
+ bass: "Electric Bass (pick)",
1569
+ chord: "Overdriven Guitar"
1570
+ },
1571
+ // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
1572
+ orchestra: {
1573
+ displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
1574
+ description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
1575
+ melody: "French Horn",
1576
+ submelody: "Pizzicato Strings",
1577
+ bass: "Cello",
1578
+ chord: "Tremolo Strings"
1579
+ },
1580
+ japanese_wa: {
1581
+ displayName: "\u548C\u98A8\u30FB\u96C5",
1582
+ description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
1583
+ melody: "Koto",
1584
+ submelody: "Shamisen",
1585
+ bass: "Taiko Drum",
1586
+ chord: "Shakuhachi"
1587
+ },
1588
+ arabic_exotic: {
1589
+ displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
1590
+ description: "\u30B7\u30BF\u30FC\u30EB\u3084\u30D0\u30B0\u30D1\u30A4\u30D7\u306B\u3088\u308B\u3001\u7570\u56FD\u60C5\u7DD2\u6EA2\u308C\u308B\u30B5\u30A6\u30F3\u30C9\u3002",
1591
+ melody: "Sitar",
1592
+ submelody: "Bagpipe",
1593
+ bass: "Fretless Bass",
1594
+ chord: "Kalimba"
1595
+ },
1596
+ // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
1597
+ fantasy_rpg: {
1598
+ displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
1599
+ description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
1600
+ melody: "Ocarina",
1601
+ submelody: "Celesta",
1602
+ bass: "Timpani",
1603
+ chord: "Orchestral Harp"
1604
+ },
1605
+ ambient_cloud: {
1606
+ displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
1607
+ description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
1608
+ melody: "Lead 6 (voice)",
1609
+ submelody: "Music Box",
1610
+ bass: "Synth Bass 1",
1611
+ chord: "Pad 7 (halo)"
1612
+ },
1613
+ retro_game: {
1614
+ displayName: "8-bit \u30EC\u30C8\u30ED",
1615
+ description: "\u77E9\u5F62\u6CE2\u3092\u60F3\u8D77\u3055\u305B\u308B\u3001\u521D\u671F\u30B2\u30FC\u30E0\u6A5F\u306E\u3088\u3046\u306A\u61D0\u304B\u3057\u3044\u97FF\u304D\u3002",
1616
+ melody: "Lead 1 (square)",
1617
+ submelody: "Lead 2 (sawtooth)",
1618
+ bass: "Synth Bass 1",
1619
+ chord: "Clavinet"
1620
+ }
1621
+ };
1622
+
1514
1623
  // node_modules/.pnpm/@onjmin+koe@1.0.3/node_modules/@onjmin/koe/dist/index.js
1515
1624
  var MAGIC = 1263486208;
1516
1625
  function parseKoeHeader(headerBytes) {
@@ -3960,6 +4069,7 @@ var parseMML = (mml, options = {}) => {
3960
4069
  bpm,
3961
4070
  tokenTracks: collectTokens ? tokenTracks : void 0,
3962
4071
  lyrics: collectLyrics ? /* @__PURE__ */ new Map() : void 0,
4072
+ mergedTrackCount: 0,
3963
4073
  meta: {}
3964
4074
  };
3965
4075
  }
@@ -3972,13 +4082,24 @@ var parseMML = (mml, options = {}) => {
3972
4082
  const fullMML = stripLyrics(noMeta).replace(endRegex, "").replace(/[\n\r]+/g, " ").trim();
3973
4083
  const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
3974
4084
  let trackIndex = 0;
4085
+ let sourceTrackIndex = 0;
3975
4086
  let octave = 4;
3976
4087
  let currentStep = 0;
3977
4088
  let baseLength = 16;
4089
+ const contributors = /* @__PURE__ */ new Map();
4090
+ const recordContributor = () => {
4091
+ let set = contributors.get(trackIndex);
4092
+ if (!set) {
4093
+ set = /* @__PURE__ */ new Set();
4094
+ contributors.set(trackIndex, set);
4095
+ }
4096
+ set.add(sourceTrackIndex);
4097
+ };
3978
4098
  for (const rawPart of parts) {
3979
4099
  const part = rawPart.trim();
3980
4100
  if (part.startsWith("@")) {
3981
4101
  let idx = Number.parseInt(part.substring(1), 10);
4102
+ sourceTrackIndex = idx;
3982
4103
  if (clampTrackCount !== void 0 && idx >= clampTrackCount)
3983
4104
  idx = clampTrackCount - 1;
3984
4105
  trackIndex = idx;
@@ -4102,6 +4223,7 @@ var parseMML = (mml, options = {}) => {
4102
4223
  }
4103
4224
  if (j < body.length && body[j] === "]") j++;
4104
4225
  const steps = parseLength();
4226
+ if (chordNotes.length > 0) recordContributor();
4105
4227
  for (const p of chordNotes) {
4106
4228
  placements.push({
4107
4229
  trackIndex,
@@ -4125,6 +4247,7 @@ var parseMML = (mml, options = {}) => {
4125
4247
  }
4126
4248
  const midiPitch = (octave + 1) * 12 + pitch;
4127
4249
  const steps = parseLength();
4250
+ recordContributor();
4128
4251
  placements.push({
4129
4252
  trackIndex,
4130
4253
  startStep: currentStep,
@@ -4138,11 +4261,16 @@ var parseMML = (mml, options = {}) => {
4138
4261
  }
4139
4262
  }
4140
4263
  }
4264
+ let mergedTrackCount = 0;
4265
+ for (const set of contributors.values()) {
4266
+ if (set.size >= 2) mergedTrackCount++;
4267
+ }
4141
4268
  return {
4142
4269
  placements,
4143
4270
  bpm,
4144
4271
  tokenTracks: collectTokens ? tokenTracks : void 0,
4145
4272
  lyrics,
4273
+ mergedTrackCount,
4146
4274
  meta
4147
4275
  };
4148
4276
  };
@@ -4204,6 +4332,8 @@ var createSequencer = (options) => {
4204
4332
  let active = false;
4205
4333
  let fromStepValue = 0;
4206
4334
  let trackVolumeMap = /* @__PURE__ */ new Map();
4335
+ let lastRealTime = -1;
4336
+ let lastAudioTime = -1;
4207
4337
  let isLooping = false;
4208
4338
  let loopStartStep = 0;
4209
4339
  let loopEndStep = 0;
@@ -4277,6 +4407,21 @@ var createSequencer = (options) => {
4277
4407
  const sps = secondsPerStep();
4278
4408
  const time = options.getAudioTime() - startTime;
4279
4409
  const soloId = options.getSoloTrackId();
4410
+ const nowReal = performance.now() / 1e3;
4411
+ if (lastRealTime > 0 && lastAudioTime >= 0) {
4412
+ const realDelta = nowReal - lastRealTime;
4413
+ const audioDelta = time - lastAudioTime;
4414
+ if (realDelta > 0.5 || audioDelta > 0.5) {
4415
+ console.warn(
4416
+ `[sequencer] Interruption detected (realDelta: ${realDelta.toFixed(3)}s, audioDelta: ${audioDelta.toFixed(3)}s). Stopping playback.`
4417
+ );
4418
+ stop();
4419
+ options.onEnd(true);
4420
+ return;
4421
+ }
4422
+ }
4423
+ lastRealTime = nowReal;
4424
+ lastAudioTime = time;
4280
4425
  for (const track of options.getTracks()) {
4281
4426
  trackVolumeMap.set(track.id, track.volume);
4282
4427
  }
@@ -4353,7 +4498,7 @@ var createSequencer = (options) => {
4353
4498
  const lastDuration = last?.duration ?? 0;
4354
4499
  if (nowIndex >= timeline.length && time > lastWhen + lastDuration + 0.1) {
4355
4500
  stop();
4356
- options.onEnd();
4501
+ options.onEnd(false);
4357
4502
  }
4358
4503
  }
4359
4504
  };
@@ -4394,6 +4539,8 @@ var createSequencer = (options) => {
4394
4539
  }
4395
4540
  loopBase = 0;
4396
4541
  lastPlayStep = fromStepValue - 1e-4;
4542
+ lastRealTime = -1;
4543
+ lastAudioTime = -1;
4397
4544
  intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
4398
4545
  animationId = requestAnimationFrame(animate);
4399
4546
  };
@@ -4591,11 +4738,11 @@ var DAW_CSS = `
4591
4738
  }
4592
4739
  .dtm-btn:active { transform: translate(3px,3px); box-shadow: none; }
4593
4740
  .dtm-btn:disabled { opacity: .3; cursor: default; box-shadow: none; }
4741
+ .dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }
4594
4742
  .dtm-btn--primary { border-color: var(--dtm-primary); background: var(--dtm-primary); color: var(--dtm-pfg); }
4595
4743
  .dtm-btn--success { border-color: var(--dtm-success); background: var(--dtm-success); color: var(--c-black); }
4596
4744
  .dtm-btn--danger { border-color: var(--dtm-danger); background: var(--dtm-danger); color: var(--c-white); }
4597
4745
  .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
4746
  .dtm-btn--icon { padding: 0; }
4600
4747
 
4601
4748
  /* \u2500\u2500\u2500 \u30A2\u30A4\u30B3\u30F3\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
@@ -5229,6 +5376,18 @@ var DAW_CSS = `
5229
5376
  }
5230
5377
 
5231
5378
  .dtm-hidden { display: none !important; }
5379
+ /* \u8AAD\u8FBC\u6642\u306E\u63A7\u3048\u3081\u306A\u304A\u77E5\u3089\u305B\uFF08\u4F8B: \u30B7\u30F3\u30D7\u30EB\u30E2\u30FC\u30C9\u3067\u306E\u30C8\u30E9\u30C3\u30AF\u5408\u7B97\uFF09\u3002\u4E3B\u5F35\u3057\u3059\u304E\u306A\u3044 muted \u8868\u793A\u3002 */
5380
+ .dtm-load-note {
5381
+ margin: 6px 0 0;
5382
+ padding: 0 2px;
5383
+ font-family: var(--dtm-font);
5384
+ font-size: 10px;
5385
+ line-height: 1.5;
5386
+ letter-spacing: .04em;
5387
+ color: var(--dtm-muted);
5388
+ opacity: .85;
5389
+ }
5390
+ .dtm-load-note::before { content: "\u24D8 "; }
5232
5391
  .dtm-grow { flex: 1 1 auto; }
5233
5392
  .dtm-lyric-icon {
5234
5393
  flex: 0 0 auto;
@@ -5328,6 +5487,62 @@ var DAW_CSS = `
5328
5487
  white-space: nowrap;
5329
5488
  }
5330
5489
  .dtm-player-mml-link:hover { color: var(--dtm-primary); }
5490
+ .dtm-player-more-container {
5491
+ position: relative;
5492
+ display: inline-flex;
5493
+ align-items: center;
5494
+ }
5495
+ .dtm-player-more-btn {
5496
+ background: transparent;
5497
+ border: none;
5498
+ color: var(--dtm-muted);
5499
+ cursor: pointer;
5500
+ padding: 2px;
5501
+ display: flex;
5502
+ align-items: center;
5503
+ justify-content: center;
5504
+ border-radius: 50%;
5505
+ width: 24px;
5506
+ height: 24px;
5507
+ transition: color 0.15s, background-color 0.15s;
5508
+ }
5509
+ .dtm-player-more-btn:hover,
5510
+ .dtm-player-more-btn.is-active {
5511
+ color: var(--dtm-text);
5512
+ background: var(--dtm-border2);
5513
+ }
5514
+ .dtm-player-menu {
5515
+ position: absolute;
5516
+ top: calc(100% + 4px);
5517
+ right: 0;
5518
+ background: var(--dtm-deep);
5519
+ border: 2px solid var(--dtm-border2);
5520
+ box-shadow: 4px 4px 0 var(--c-black);
5521
+ z-index: 200;
5522
+ display: flex;
5523
+ flex-direction: column;
5524
+ padding: 4px 0;
5525
+ min-width: 130px;
5526
+ font-family: var(--dtm-font);
5527
+ }
5528
+ .dtm-player-menu-item {
5529
+ background: transparent;
5530
+ border: none;
5531
+ color: var(--dtm-text);
5532
+ padding: 6px 12px;
5533
+ text-align: left;
5534
+ cursor: pointer;
5535
+ font-size: 11px;
5536
+ font-family: inherit;
5537
+ white-space: nowrap;
5538
+ width: 100%;
5539
+ box-sizing: border-box;
5540
+ transition: background-color 0.1s, color 0.1s;
5541
+ }
5542
+ .dtm-player-menu-item:hover {
5543
+ background: var(--dtm-primary);
5544
+ color: var(--dtm-pfg);
5545
+ }
5331
5546
  .dtm-player-emoji {
5332
5547
  position: relative;
5333
5548
  display: inline-flex;
@@ -5603,6 +5818,75 @@ var showBalloon = (balloonEl) => {
5603
5818
  hideActiveBalloon();
5604
5819
  }, 3e3);
5605
5820
  };
5821
+ var copyToClipboard = async (doc, text) => {
5822
+ try {
5823
+ await navigator.clipboard.writeText(text);
5824
+ return true;
5825
+ } catch {
5826
+ try {
5827
+ const textarea = doc.createElement("textarea");
5828
+ textarea.value = text;
5829
+ textarea.style.position = "fixed";
5830
+ textarea.style.opacity = "0";
5831
+ doc.body.appendChild(textarea);
5832
+ textarea.select();
5833
+ const ok = doc.execCommand("copy");
5834
+ doc.body.removeChild(textarea);
5835
+ return ok;
5836
+ } catch {
5837
+ return false;
5838
+ }
5839
+ }
5840
+ };
5841
+ var toBase64Url = (bytes) => {
5842
+ let bin = "";
5843
+ for (let i = 0; i < bytes.length; i++) {
5844
+ bin += String.fromCharCode(bytes[i]);
5845
+ }
5846
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5847
+ };
5848
+ var HIRAGANA_START = 12353;
5849
+ var HIRAGANA_END = 12447;
5850
+ var KATAKANA_START = 12449;
5851
+ var KATAKANA_END = 12543;
5852
+ var PROLONGED_MARK = 12540;
5853
+ var SHIFT_KATAKANA = 255;
5854
+ var VALUE_PROLONGED = 223;
5855
+ var customEncode = (str) => {
5856
+ const bytes = [];
5857
+ for (let i = 0; i < str.length; i++) {
5858
+ const code = str.charCodeAt(i);
5859
+ if (code <= 127) {
5860
+ bytes.push(code);
5861
+ } else if (code === PROLONGED_MARK) {
5862
+ bytes.push(VALUE_PROLONGED);
5863
+ } else if (code >= HIRAGANA_START && code <= HIRAGANA_END) {
5864
+ bytes.push(128 + (code - HIRAGANA_START));
5865
+ } else if (code >= KATAKANA_START && code <= KATAKANA_END) {
5866
+ bytes.push(SHIFT_KATAKANA);
5867
+ bytes.push(128 + (code - 96 - HIRAGANA_START));
5868
+ }
5869
+ }
5870
+ return new Uint8Array(bytes);
5871
+ };
5872
+ var encodeMml = async (mml) => {
5873
+ try {
5874
+ if (typeof CompressionStream !== "undefined") {
5875
+ const cs = new CompressionStream("gzip");
5876
+ const w = cs.writable.getWriter();
5877
+ w.write(customEncode(mml));
5878
+ w.close();
5879
+ const buf = await new Response(cs.readable).arrayBuffer();
5880
+ return `z.${toBase64Url(new Uint8Array(buf))}`;
5881
+ }
5882
+ } catch (e) {
5883
+ console.warn(
5884
+ "[dtm] CompressionStream failed, fallback to encodeURIComponent",
5885
+ e
5886
+ );
5887
+ }
5888
+ return `u.${encodeURIComponent(mml)}`;
5889
+ };
5606
5890
  var mountMmlPlayer = (target, mml, options = {}) => {
5607
5891
  injectStyles(target.ownerDocument ?? document);
5608
5892
  const {
@@ -5748,6 +6032,83 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5748
6032
  emojiEls.push(em);
5749
6033
  emojiByTrack.set(index, em);
5750
6034
  }
6035
+ const menuContainer = doc.createElement("div");
6036
+ menuContainer.className = "dtm-player-more-container";
6037
+ const moreBtn = doc.createElement("button");
6038
+ moreBtn.type = "button";
6039
+ moreBtn.className = "dtm-player-more-btn";
6040
+ moreBtn.innerHTML = icon("more", 14);
6041
+ moreBtn.title = "\u30E1\u30CB\u30E5\u30FC";
6042
+ menuContainer.appendChild(moreBtn);
6043
+ const menuDropdown = doc.createElement("div");
6044
+ menuDropdown.className = "dtm-player-menu";
6045
+ menuDropdown.style.display = "none";
6046
+ const copyMmlItem = doc.createElement("button");
6047
+ copyMmlItem.type = "button";
6048
+ copyMmlItem.className = "dtm-player-menu-item";
6049
+ copyMmlItem.textContent = "MML\u30B3\u30D4\u30FC";
6050
+ const embedItem = doc.createElement("button");
6051
+ embedItem.type = "button";
6052
+ embedItem.className = "dtm-player-menu-item";
6053
+ embedItem.textContent = "\u57CB\u3081\u8FBC\u3080";
6054
+ menuDropdown.appendChild(copyMmlItem);
6055
+ menuDropdown.appendChild(embedItem);
6056
+ menuContainer.appendChild(menuDropdown);
6057
+ mmlHeader.appendChild(menuContainer);
6058
+ const toggleMenu = (show) => {
6059
+ const visible = show !== void 0 ? show : menuDropdown.style.display === "none";
6060
+ menuDropdown.style.display = visible ? "flex" : "none";
6061
+ if (visible) {
6062
+ moreBtn.classList.add("is-active");
6063
+ doc.addEventListener("click", handleOutsideClick);
6064
+ } else {
6065
+ moreBtn.classList.remove("is-active");
6066
+ doc.removeEventListener("click", handleOutsideClick);
6067
+ }
6068
+ };
6069
+ const handleOutsideClick = (e) => {
6070
+ if (!menuContainer.contains(e.target)) {
6071
+ toggleMenu(false);
6072
+ }
6073
+ };
6074
+ moreBtn.addEventListener("click", (e) => {
6075
+ e.stopPropagation();
6076
+ toggleMenu();
6077
+ });
6078
+ copyMmlItem.addEventListener("click", async (e) => {
6079
+ e.stopPropagation();
6080
+ const success = await copyToClipboard(doc, mml);
6081
+ if (success) {
6082
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F\uFF01";
6083
+ } else {
6084
+ copyMmlItem.textContent = "\u30B3\u30D4\u30FC\u5931\u6557";
6085
+ }
6086
+ setTimeout(() => {
6087
+ copyMmlItem.textContent = "MML\u30B3\u30D4\u30FC";
6088
+ }, 2e3);
6089
+ });
6090
+ embedItem.addEventListener("click", async (e) => {
6091
+ e.stopPropagation();
6092
+ embedItem.textContent = "\u751F\u6210\u4E2D...";
6093
+ try {
6094
+ const embedBase = options.embedUrl ?? "https://onjmin.github.io/dtm/demo/embed.html";
6095
+ const payload = await encodeMml(mml);
6096
+ const url2 = `${embedBase}#${payload}`;
6097
+ const snippet = `<iframe src="${url2}" width="100%" height="260" frameborder="0" loading="lazy" title="@onjmin/dtm player"></iframe>`;
6098
+ const success = await copyToClipboard(doc, snippet);
6099
+ if (success) {
6100
+ embedItem.textContent = "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F\uFF01";
6101
+ } else {
6102
+ embedItem.textContent = "\u30B3\u30D4\u30FC\u5931\u6557";
6103
+ }
6104
+ } catch (err2) {
6105
+ console.error("[dtm] failed to generate embed snippet", err2);
6106
+ embedItem.textContent = "\u751F\u6210\u5931\u6557";
6107
+ }
6108
+ setTimeout(() => {
6109
+ embedItem.textContent = "\u57CB\u3081\u8FBC\u3080";
6110
+ }, 2e3);
6111
+ });
5751
6112
  const promotedToImage = /* @__PURE__ */ new Set();
5752
6113
  for (const [index, lt] of lyricTracks) {
5753
6114
  const em = emojiByTrack.get(index);
@@ -6056,7 +6417,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6056
6417
  onTick: (step) => {
6057
6418
  renderPlayhead(step);
6058
6419
  },
6059
- onEnd: () => finish(),
6420
+ onEnd: (interrupted) => finish(),
6060
6421
  stepsPerBar: STEPS_PER_BAR
6061
6422
  });
6062
6423
  let playing = false;
@@ -6151,6 +6512,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6151
6512
  else play();
6152
6513
  });
6153
6514
  const destroy = () => {
6515
+ doc.removeEventListener("click", handleOutsideClick);
6154
6516
  seq.stop();
6155
6517
  peekVoices()?.stopStream();
6156
6518
  if (activePlayer === instance) activePlayer = null;
@@ -7069,6 +7431,10 @@ var mountDAW = (target, options = {}) => {
7069
7431
  0,
7070
7432
  Math.floor(step / snapGridSteps) * snapGridSteps
7071
7433
  );
7434
+ if (playbackState === "paused") {
7435
+ playbackState = "stopped";
7436
+ updateTransport();
7437
+ }
7072
7438
  redrawAll();
7073
7439
  });
7074
7440
  setDrawOffset(currentOffsetX, currentOffsetY);
@@ -7144,9 +7510,14 @@ var mountDAW = (target, options = {}) => {
7144
7510
  }
7145
7511
  redrawAll();
7146
7512
  },
7147
- onEnd: () => {
7148
- playbackState = "stopped";
7149
- currentPlayStep = 0;
7513
+ onEnd: (interrupted) => {
7514
+ if (interrupted) {
7515
+ playbackState = "paused";
7516
+ pausedPlayStep = currentPlayStep;
7517
+ } else {
7518
+ playbackState = "stopped";
7519
+ currentPlayStep = 0;
7520
+ }
7150
7521
  updateTransport();
7151
7522
  redrawAll();
7152
7523
  },
@@ -7236,13 +7607,13 @@ var mountDAW = (target, options = {}) => {
7236
7607
  redrawAll();
7237
7608
  };
7238
7609
  const togglePlay = () => {
7239
- if (playbackState === "playing") stop();
7610
+ if (playbackState === "playing") pause();
7240
7611
  else play();
7241
7612
  };
7242
7613
  const updateTransport = () => {
7243
7614
  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>`;
7615
+ const label = playing ? "\u4E00\u6642\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7616
+ refs.playBtn.innerHTML = `${icon(playing ? "pause" : "play")}<span>${label}</span>`;
7246
7617
  refs.playBtn.classList.toggle("dtm-play--stop", playing);
7247
7618
  };
7248
7619
  const updateUndoRedo = () => {
@@ -7631,20 +8002,25 @@ var mountDAW = (target, options = {}) => {
7631
8002
  };
7632
8003
  const loadMML = (mml) => {
7633
8004
  if (!mml) return;
8005
+ stop();
7634
8006
  clearAll();
7635
8007
  for (const t of trackStates) t.core.setLoadMode(true);
7636
8008
  const {
7637
8009
  placements,
7638
8010
  bpm: parsedBpm,
7639
8011
  lyrics,
7640
- meta
8012
+ meta,
8013
+ mergedTrackCount
7641
8014
  } = parseMML(mml, {
7642
8015
  stepsPerBar: renderConfig.stepsPerBar,
7643
8016
  collectLyrics: true,
7644
8017
  // このDAWのトラック数を超えるチャンネルはベースへ畳み込む(従来挙動)
7645
8018
  clampTrackCount: trackStates.length
7646
8019
  });
7647
- currentInstrument = meta.instrument ?? "";
8020
+ if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
8021
+ currentInstrument = meta.instrument;
8022
+ options.onInstrumentChange?.(meta.instrument);
8023
+ }
7648
8024
  if (meta.drum && drumPatterns[meta.drum]) {
7649
8025
  currentDrumPattern = meta.drum;
7650
8026
  refs.drumSelect.value = meta.drum;
@@ -7696,6 +8072,13 @@ var mountDAW = (target, options = {}) => {
7696
8072
  redrawAll();
7697
8073
  updateTrackPanel();
7698
8074
  updateUndoRedo();
8075
+ if (!isAdvanced && mergedTrackCount > 0) {
8076
+ refs.mmlLoadNote.textContent = "\u30B7\u30F3\u30D7\u30EB\u30E2\u30FC\u30C9\u306E\u305F\u3081\u3001\u4E00\u90E8\u306E\u30C8\u30E9\u30C3\u30AF\u3092\u5408\u7B97\u3057\u3066\u8AAD\u307F\u8FBC\u307F\u307E\u3057\u305F";
8077
+ refs.mmlLoadNote.classList.remove("dtm-hidden");
8078
+ } else {
8079
+ refs.mmlLoadNote.textContent = "";
8080
+ refs.mmlLoadNote.classList.add("dtm-hidden");
8081
+ }
7699
8082
  };
7700
8083
  const applyChord = () => {
7701
8084
  const active = getActive();
@@ -7728,6 +8111,7 @@ var mountDAW = (target, options = {}) => {
7728
8111
  applyMidiSelection(midi, selected);
7729
8112
  };
7730
8113
  const applyMidiSelection = (midi, selectedIndices) => {
8114
+ stop();
7731
8115
  clearAll();
7732
8116
  for (const t of trackStates) t.core.setLoadMode(true);
7733
8117
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -8159,6 +8543,25 @@ var mountDAW = (target, options = {}) => {
8159
8543
  const setLoading = (loading) => {
8160
8544
  refs.topbar.classList.toggle("is-loading", loading);
8161
8545
  };
8546
+ const getCurrentPlayStep = () => {
8547
+ if (playbackState === "playing") return currentPlayStep;
8548
+ if (playbackState === "paused") return pausedPlayStep;
8549
+ return playStartStep;
8550
+ };
8551
+ const forcePauseAt = (step) => {
8552
+ playStartStep = step;
8553
+ pausedPlayStep = step;
8554
+ currentPlayStep = step;
8555
+ playbackState = "paused";
8556
+ const canvas = getGridCanvas();
8557
+ currentOffsetX = Math.max(
8558
+ 0,
8559
+ step * renderConfig.stepWidth - canvas.width * 0.5
8560
+ );
8561
+ setDrawOffset(currentOffsetX, currentOffsetY);
8562
+ updateTransport();
8563
+ redrawAll();
8564
+ };
8162
8565
  return {
8163
8566
  play,
8164
8567
  pause,
@@ -8196,9 +8599,12 @@ var mountDAW = (target, options = {}) => {
8196
8599
  exportMIDI: exportMIDI2,
8197
8600
  setBpm,
8198
8601
  getPlaybackState: () => playbackState,
8602
+ getCurrentPlayStep,
8603
+ forcePauseAt,
8199
8604
  setLoading,
8200
8605
  destroy: () => {
8201
8606
  sequencer.stop();
8607
+ options.singingVoices?.stopStream();
8202
8608
  resizeObserver.disconnect();
8203
8609
  document.removeEventListener("pointermove", onPointerMove);
8204
8610
  document.removeEventListener("pointerup", onPointerUp);
@@ -8208,110 +8614,6 @@ var mountDAW = (target, options = {}) => {
8208
8614
  };
8209
8615
  };
8210
8616
 
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
8617
  // src/headless-player.ts
8316
8618
  var STEPS_PER_BAR2 = 192;
8317
8619
  var playMML = (mml, options = {}) => {
@@ -8362,7 +8664,7 @@ var playMML = (mml, options = {}) => {
8362
8664
  },
8363
8665
  onTick: () => {
8364
8666
  },
8365
- onEnd: () => finish(),
8667
+ onEnd: (interrupted) => finish(),
8366
8668
  stepsPerBar: STEPS_PER_BAR2
8367
8669
  });
8368
8670
  const finish = () => {
@@ -9182,11 +9484,13 @@ var createDtmStudio = async (options = {}) => {
9182
9484
  const mountedPlayers = [];
9183
9485
  const mountedModeSwitches = [];
9184
9486
  const mountEditor = (target, opts = {}) => {
9185
- const { preset, presetUI, ...dawOverrides } = opts;
9487
+ const { preset, presetUI, onInstrumentChange, ...dawOverrides } = opts;
9186
9488
  const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
9187
9489
  const trackIds = tracks.map((t) => t.id);
9188
9490
  const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
9189
- let editorPreset = presetKey;
9491
+ const meta = opts.initialMML ? parseMmlMeta(opts.initialMML) : {};
9492
+ const initialPreset = meta.instrument && INSTRUMENT_PRESETS[meta.instrument] ? meta.instrument : presetKey;
9493
+ let editorPreset = initialPreset;
9190
9494
  const isAdvancedMode = dawOverrides.mode === "advanced";
9191
9495
  const playNote = (e) => {
9192
9496
  const sf = resolveSoundFont(
@@ -9204,6 +9508,14 @@ var createDtmStudio = async (options = {}) => {
9204
9508
  duration: e.duration
9205
9509
  });
9206
9510
  };
9511
+ let presetSelect = null;
9512
+ const handleInstrumentChange = (key) => {
9513
+ editorPreset = key;
9514
+ if (presetSelect) {
9515
+ presetSelect.setValue(key);
9516
+ }
9517
+ onInstrumentChange?.(key);
9518
+ };
9207
9519
  const base = {
9208
9520
  getAudioTime: () => audioCtx.currentTime,
9209
9521
  onResumeAudio: resumeAudio,
@@ -9212,19 +9524,19 @@ var createDtmStudio = async (options = {}) => {
9212
9524
  singingVoices,
9213
9525
  parseMidi,
9214
9526
  onToggleRecord,
9527
+ onInstrumentChange: handleInstrumentChange,
9215
9528
  ...dawOverrides
9216
9529
  };
9217
9530
  const daw = mountDAW(target, base);
9218
9531
  mountedEditors.push(daw);
9219
9532
  const wantPresetUI = presetUI ?? features.presetUI;
9220
- let presetSelect = null;
9221
9533
  if (wantPresetUI) {
9222
9534
  editorPresetSelects.get(target)?.destroy();
9223
9535
  const rollEl = target.querySelector('[data-dtm="roll"]');
9224
9536
  presetSelect = mountPresetSelect(target, {
9225
9537
  getDaw: () => daw,
9226
9538
  getTrackIds: () => trackIds,
9227
- value: presetKey,
9539
+ value: initialPreset,
9228
9540
  loadingTarget: rollEl ?? target,
9229
9541
  position: "prepend",
9230
9542
  // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
@@ -9234,10 +9546,10 @@ var createDtmStudio = async (options = {}) => {
9234
9546
  });
9235
9547
  editorPresetSelects.set(target, presetSelect);
9236
9548
  }
9237
- daw.setInstrument(presetKey);
9549
+ daw.setInstrument(initialPreset);
9238
9550
  daw.setLoading?.(true);
9239
9551
  void loadPreset(
9240
- presetKey,
9552
+ initialPreset,
9241
9553
  trackIds,
9242
9554
  isAdvancedMode ? "advanced" : "simple"
9243
9555
  ).finally(() => {
@@ -9251,7 +9563,17 @@ var createDtmStudio = async (options = {}) => {
9251
9563
  const i = mountedEditors.indexOf(daw);
9252
9564
  if (i >= 0) mountedEditors.splice(i, 1);
9253
9565
  };
9254
- return { ...daw, destroy };
9566
+ return {
9567
+ ...daw,
9568
+ setInstrument: (name) => {
9569
+ daw.setInstrument(name);
9570
+ editorPreset = name;
9571
+ if (presetSelect) {
9572
+ presetSelect.setValue(name);
9573
+ }
9574
+ },
9575
+ destroy
9576
+ };
9255
9577
  };
9256
9578
  const mountModeSwitch = (target, opts) => {
9257
9579
  const doc = target.ownerDocument;