@onjmin/dtm 2.1.0 → 2.1.1

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
@@ -4213,7 +4213,7 @@ var NATURAL_STEPS2 = {
4213
4213
  };
4214
4214
  var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
4215
4215
  var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
4216
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w-]+)/gi;
4216
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w:-]+)/gi;
4217
4217
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
4218
4218
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
4219
4219
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
@@ -8734,6 +8734,27 @@ var DAW_CSS = `
8734
8734
  .dtm-player-play:active { transform: translate(2px, 2px); box-shadow: none; }
8735
8735
  .dtm-player-play--stop { background: var(--dtm-danger); }
8736
8736
  .dtm-player-play:disabled { opacity: 0.4; cursor: default; }
8737
+ .dtm-player-loop {
8738
+ display: inline-flex;
8739
+ align-items: center;
8740
+ justify-content: center;
8741
+ width: 28px;
8742
+ height: 28px;
8743
+ padding: 0;
8744
+ border: 2px solid var(--dtm-border2);
8745
+ background: var(--dtm-bg);
8746
+ color: var(--dtm-muted);
8747
+ cursor: pointer;
8748
+ flex: 0 0 auto;
8749
+ }
8750
+ .dtm-player-loop:active { transform: translate(1px, 1px); }
8751
+ .dtm-player-loop--on {
8752
+ border-color: var(--c-black);
8753
+ background: var(--dtm-gold);
8754
+ color: var(--c-black);
8755
+ box-shadow: 2px 2px 0 var(--c-black);
8756
+ }
8757
+ .dtm-player-loop:disabled { opacity: 0.4; cursor: default; }
8737
8758
  .dtm-player-beat-row {
8738
8759
  display: flex;
8739
8760
  align-items: center;
@@ -9392,6 +9413,12 @@ var FALLBACK_VOCAL_ICON = Chip_default;
9392
9413
  var STEPS_PER_BEAT2 = 48;
9393
9414
  var STEPS_PER_BAR2 = 192;
9394
9415
  var DEFAULT_TRACK_COLORS = ["#00e436", "#29adff", "#ff77a8", "#ffec27"];
9416
+ var parseLoopMeta = (mml) => {
9417
+ const m = mml.match(/^#\s*loop\s*=\s*(on|off|1|0|true|false)\s*$/im);
9418
+ if (!m) return null;
9419
+ const v = m[1].toLowerCase();
9420
+ return v === "on" || v === "1" || v === "true";
9421
+ };
9395
9422
  var activePlayer = null;
9396
9423
  var agreedModelsInSession = /* @__PURE__ */ new Set();
9397
9424
  var LYRIC_MODEL_LABELS = {
@@ -9631,6 +9658,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9631
9658
  const colors = options.trackColors ?? DEFAULT_TRACK_COLORS;
9632
9659
  const useSynth = options.synth ?? !options.onPlayNote;
9633
9660
  const secondsPerStep = 60 / bpm / STEPS_PER_BEAT2;
9661
+ let loopEnabled = options.loop ?? parseLoopMeta(mml) ?? false;
9634
9662
  const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
9635
9663
  (a, b) => a - b
9636
9664
  );
@@ -9719,6 +9747,13 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9719
9747
  playBtn.className = "dtm-player-play";
9720
9748
  playBtn.innerHTML = icon("play", 12);
9721
9749
  playBtn.disabled = trackIndices.length === 0;
9750
+ const loopBtn = doc.createElement("button");
9751
+ loopBtn.type = "button";
9752
+ loopBtn.className = "dtm-player-loop";
9753
+ loopBtn.innerHTML = icon("loop", 12);
9754
+ loopBtn.title = loopEnabled ? "LOOP ON" : "LOOP OFF";
9755
+ loopBtn.classList.toggle("dtm-player-loop--on", loopEnabled);
9756
+ loopBtn.disabled = trackIndices.length === 0;
9722
9757
  const mutedTracks = /* @__PURE__ */ new Set();
9723
9758
  const labelByTrack = /* @__PURE__ */ new Map();
9724
9759
  const emojiByTrack = /* @__PURE__ */ new Map();
@@ -10151,7 +10186,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
10151
10186
  chordEl.className = "dtm-player-chord";
10152
10187
  chordEl.textContent = "";
10153
10188
  beatRow.appendChild(chordEl);
10154
- head.append(playBtn, beatRow, dots, mmlHeader);
10189
+ head.append(playBtn, loopBtn, beatRow, dots, mmlHeader);
10155
10190
  root.appendChild(head);
10156
10191
  const seekRow = doc.createElement("div");
10157
10192
  seekRow.className = "dtm-player-seek-row";
@@ -10451,6 +10486,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
10451
10486
  getPlayStartStep: () => 0,
10452
10487
  getDrumPattern: (currentBar) => resolveDrumPattern(drumPatternName, drumPatternDict, currentBar),
10453
10488
  getSoloTrackId: () => null,
10489
+ getLoop: () => loopEnabled,
10454
10490
  getAudioTime,
10455
10491
  onPlayNote: (e) => {
10456
10492
  const trackIdx = Number(e.trackId);
@@ -10567,8 +10603,16 @@ var mountMmlPlayer = (target, mml, options = {}) => {
10567
10603
  if (streaming && !skipSinging) {
10568
10604
  const v = ensureVoices();
10569
10605
  v.setVolume(trackVolume / 100 * (masterVolume / 100));
10606
+ let loopLengthSec;
10607
+ let loopStartSec;
10608
+ if (loopEnabled) {
10609
+ loopStartSec = 0;
10610
+ loopLengthSec = maxStep * secondsPerStep;
10611
+ }
10570
10612
  v.startStream(tracks, seq.getStartTime(), {
10571
10613
  isAudible: (t) => !mutedTracks.has(Number(t.id)),
10614
+ loopLengthSec,
10615
+ loopStartSec,
10572
10616
  onLateSkip: () => {
10573
10617
  showPlayerMessage(
10574
10618
  "\u97F3\u58F0\u5408\u6210\u304C\u9593\u306B\u5408\u308F\u306A\u3044\u305F\u3081\u3001\u4E00\u90E8\u306E\u767A\u97F3\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F"
@@ -10640,6 +10684,14 @@ var mountMmlPlayer = (target, mml, options = {}) => {
10640
10684
  if (playing) pause();
10641
10685
  else play(Number(seekInput.value));
10642
10686
  });
10687
+ loopBtn.addEventListener("click", () => {
10688
+ loopEnabled = !loopEnabled;
10689
+ loopBtn.classList.toggle("dtm-player-loop--on", loopEnabled);
10690
+ loopBtn.title = loopEnabled ? "LOOP ON" : "LOOP OFF";
10691
+ if (playing) {
10692
+ seekTo(playStep);
10693
+ }
10694
+ });
10643
10695
  const destroy = () => {
10644
10696
  doc.removeEventListener("click", handleOutsideClick);
10645
10697
  seq.stop();
@@ -11179,7 +11231,7 @@ var parseBpmMeta = (chords) => {
11179
11231
  if (Number.isNaN(val)) return null;
11180
11232
  return Math.max(40, Math.min(240, val));
11181
11233
  };
11182
- var parseLoopMeta = (chords) => {
11234
+ var parseLoopMeta2 = (chords) => {
11183
11235
  const m = chords.match(/^#\s*loop\s*=\s*(on|off|1|0|true|false)\s*$/im);
11184
11236
  if (!m) return null;
11185
11237
  const v = m[1].toLowerCase();
@@ -11356,7 +11408,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
11356
11408
  let masterVolume = options.volume ?? 80;
11357
11409
  let isLoading = false;
11358
11410
  let loadAbortId = 0;
11359
- let loopEnabled = options.loop ?? parseLoopMeta(chords) ?? false;
11411
+ let loopEnabled = options.loop ?? parseLoopMeta2(chords) ?? false;
11360
11412
  let metronomeEnabled = parseMetronomeMeta(chords);
11361
11413
  let playOffsetSec = 0;
11362
11414
  let currentPlaySec = 0;
@@ -12422,292 +12474,356 @@ var CORPUS_MEDIANS = {
12422
12474
  complementarity: 0.045
12423
12475
  };
12424
12476
 
12425
- // src/compose-drums.ts
12426
- var DRUM_STYLE_LABELS = {
12427
- eight: "8\u30D3\u30FC\u30C8",
12428
- sixteen: "16\u30D3\u30FC\u30C8",
12429
- four: "4\u3064\u6253\u3061",
12430
- shuffle: "\u30B7\u30E3\u30C3\u30D5\u30EB",
12431
- ballad: "\u30D0\u30E9\u30FC\u30C9",
12432
- rock: "\u30ED\u30C3\u30AF"
12433
- };
12434
- var KICKS = {
12435
- // [抑えめ, 標準, 最大]
12436
- eight: [
12437
- [0, 8],
12438
- [0, 6, 8],
12439
- [0, 6, 8, 14]
12440
- ],
12441
- sixteen: [
12442
- [0, 3, 8],
12443
- [0, 3, 8, 11],
12444
- [0, 3, 6, 8, 11, 14]
12445
- ],
12446
- four: [
12447
- [0, 4, 8, 12],
12448
- [0, 4, 8, 12],
12449
- [0, 4, 8, 12, 14]
12450
- ],
12451
- shuffle: [
12452
- [0, 8],
12453
- [0, 8, 11],
12454
- [0, 5, 8, 11]
12455
- ],
12456
- ballad: [[0], [0, 8], [0, 8, 12]],
12457
- rock: [
12458
- [0, 8],
12459
- [0, 3, 8],
12460
- [0, 3, 8, 10, 14]
12461
- ]
12462
- };
12463
- var SNARES = {
12464
- eight: [
12465
- [4, 12],
12466
- [4, 12],
12467
- [4, 12]
12468
- ],
12469
- sixteen: [
12470
- [4, 12],
12471
- [4, 12],
12472
- [4, 12]
12473
- ],
12474
- four: [
12475
- [4, 12],
12476
- [4, 12],
12477
- [4, 12]
12478
- ],
12479
- shuffle: [
12480
- [4, 12],
12481
- [4, 12],
12482
- [4, 12]
12483
- ],
12484
- // バラードはサイドスティック(リムショット)から入って、盛り上がりでスネアへ移る。
12485
- ballad: [[12], [4, 12], [4, 12]],
12486
- rock: [
12487
- [4, 12],
12488
- [4, 12],
12489
- [4, 12]
12490
- ]
12491
- };
12492
- var GHOSTS = {
12493
- eight: [7],
12494
- sixteen: [7, 11, 15],
12495
- four: [],
12496
- shuffle: [11],
12497
- ballad: [],
12498
- rock: [7, 15]
12499
- };
12500
- var HIHATS = {
12501
- eight: [
12502
- [0, 4, 8, 12],
12503
- [0, 2, 4, 6, 8, 10, 12, 14],
12504
- [0, 2, 4, 6, 8, 10, 12, 14]
12505
- ],
12506
- sixteen: [
12507
- [0, 2, 4, 6, 8, 10, 12, 14],
12508
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
12509
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
12510
- ],
12511
- four: [
12512
- [2, 6, 10, 14],
12513
- [0, 2, 4, 6, 8, 10, 12, 14],
12514
- [0, 2, 4, 6, 8, 10, 12, 14]
12515
- ],
12516
- shuffle: [[], [], []],
12517
- // 三連なので格子では書けない。{@link shuffleHats} が作る
12518
- ballad: [
12519
- [0, 8],
12520
- [0, 4, 8, 12],
12521
- [0, 2, 4, 6, 8, 10, 12, 14]
12522
- ],
12523
- rock: [
12524
- [0, 4, 8, 12],
12525
- [0, 2, 4, 6, 8, 10, 12, 14],
12526
- [0, 2, 4, 6, 8, 10, 12, 14]
12527
- ]
12528
- };
12529
- var FILLS = [
12530
- // スネアの16分の詰め
12477
+ // src/compose-keys.ts
12478
+ var COMPOSE_KEYS = {
12479
+ // --- 長調 (12調) ---
12480
+ key_C: {
12481
+ id: "key_C",
12482
+ name: "C",
12483
+ label: "\u30CF\u9577\u8ABF (C)",
12484
+ mode: "major",
12485
+ rootShift: 0,
12486
+ moodId: "mood_happy",
12487
+ description: "\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7D14\u7C8B\u3001\u7D20\u6734\u3001\u51FA\u767A"
12488
+ },
12489
+ key_Db: {
12490
+ id: "key_Db",
12491
+ name: "D\u266D",
12492
+ label: "\u5909\u30CB\u9577\u8ABF (D\u266D)",
12493
+ mode: "major",
12494
+ rootShift: 1,
12495
+ moodId: "mood_melancholy",
12496
+ description: "\u60B2\u3057\u307F\u3001\u6182\u9B31\u306A\u3001\u7518\u7F8E\u306A\u611F\u50B7\uFF08\u5B30\u30CF\u9577\u8ABF\u3068\u540C\u97F3\uFF09"
12497
+ },
12498
+ key_D: {
12499
+ id: "key_D",
12500
+ name: "D",
12501
+ label: "\u30CB\u9577\u8ABF (D)",
12502
+ mode: "major",
12503
+ rootShift: 2,
12504
+ moodId: "mood_triumphant",
12505
+ description: "\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0\u3001\u83EF\u3084\u304B"
12506
+ },
12507
+ key_Eb: {
12508
+ id: "key_Eb",
12509
+ name: "E\u266D",
12510
+ label: "\u5909\u30DB\u9577\u8ABF (E\u266D)",
12511
+ mode: "major",
12512
+ rootShift: 3,
12513
+ moodId: "mood_dark",
12514
+ description: "\u53B3\u3057\u3044\u3001\u304D\u3064\u3044\u3001\u305D\u308C\u3067\u3044\u3066\u611B\u306B\u6E80\u3061\u305F\u3001\u8358\u91CD"
12515
+ },
12516
+ key_E: {
12517
+ id: "key_E",
12518
+ name: "E",
12519
+ label: "\u30DB\u9577\u8ABF (E)",
12520
+ mode: "major",
12521
+ rootShift: 4,
12522
+ moodId: "mood_fierce",
12523
+ description: "\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u8352\u3005\u3057\u3044\u3001\u8F1D\u304B\u3057\u3044\u60C5\u71B1"
12524
+ },
12525
+ key_F: {
12526
+ id: "key_F",
12527
+ name: "F",
12528
+ label: "\u30D8\u9577\u8ABF (F)",
12529
+ mode: "major",
12530
+ rootShift: 5,
12531
+ moodId: "mood_fierce",
12532
+ description: "\u6012\u308A\u72C2\u3063\u305F\u3001\u6C17\u6027\u306E\u8352\u3044\u3001\u4E00\u6642\u7684\u306A\u60B2\u5606\u3001\u6FC0\u52D5"
12533
+ },
12534
+ key_Gb: {
12535
+ id: "key_Gb",
12536
+ name: "G\u266D",
12537
+ label: "\u5909\u30C8\u9577\u8ABF (G\u266D)",
12538
+ mode: "major",
12539
+ rootShift: 6,
12540
+ moodId: "mood_triumphant",
12541
+ description: "\u56F0\u96E3\u306E\u6253\u7834\u3001\u5B89\u5835\u306E\u305F\u3081\u606F\u3001\u51F1\u65CB\uFF08\u5B30\u30D8\u9577\u8ABF\u3068\u540C\u97F3\uFF09"
12542
+ },
12543
+ key_G: {
12544
+ id: "key_G",
12545
+ name: "G",
12546
+ label: "\u30C8\u9577\u8ABF (G)",
12547
+ mode: "major",
12548
+ rootShift: -5,
12549
+ moodId: "mood_solemn",
12550
+ description: "\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F\u3001\u5E83\u304C\u308A"
12551
+ },
12552
+ key_Ab: {
12553
+ id: "key_Ab",
12554
+ name: "A\u266D",
12555
+ label: "\u5909\u30A4\u9577\u8ABF (A\u266D)",
12556
+ mode: "major",
12557
+ rootShift: -4,
12558
+ moodId: "mood_dark",
12559
+ description: "\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6DF1\u9060\u306A\u7791\u60F3"
12560
+ },
12561
+ key_A: {
12562
+ id: "key_A",
12563
+ name: "A",
12564
+ label: "\u30A4\u9577\u8ABF (A)",
12565
+ mode: "major",
12566
+ rootShift: -3,
12567
+ moodId: "mood_happy",
12568
+ description: "\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D\u3001\u304D\u3089\u3073\u3084\u304B"
12569
+ },
12570
+ key_Bb: {
12571
+ id: "key_Bb",
12572
+ name: "B\u266D",
12573
+ label: "\u5909\u30ED\u9577\u8ABF (B\u266D)",
12574
+ mode: "major",
12575
+ rootShift: -2,
12576
+ moodId: "mood_happy",
12577
+ description: "\u559C\u3070\u3057\u3044\u3001\u98A8\u5909\u308F\u308A\u306A\u3001\u967D\u6C17\u306A\u3001\u8EFD\u5FEB"
12578
+ },
12579
+ key_B: {
12580
+ id: "key_B",
12581
+ name: "B",
12582
+ label: "\u30ED\u9577\u8ABF (B)",
12583
+ mode: "major",
12584
+ rootShift: -1,
12585
+ moodId: "mood_fierce",
12586
+ description: "\u3069\u304E\u3064\u3044\u3001\u5F37\u70C8\u306A\u3001\u8352\u3063\u307D\u3044\u3001\u731B\u70C8"
12587
+ },
12588
+ // --- 短調 (12調) ---
12589
+ key_Am: {
12590
+ id: "key_Am",
12591
+ name: "Am",
12592
+ label: "\u30A4\u77ED\u8ABF (Am)",
12593
+ mode: "minor",
12594
+ rootShift: 0,
12595
+ moodId: "mood_solemn",
12596
+ description: "\u67D4\u3089\u304B\u306A\u3001\u7269\u60B2\u3057\u3044\u3001\u656C\u8654\u306A\u3001\u7D20\u6734\u306A\u54C0\u6101"
12597
+ },
12598
+ key_Bbm: {
12599
+ id: "key_Bbm",
12600
+ name: "B\u266Dm",
12601
+ label: "\u5909\u30ED\u77ED\u8ABF (B\u266Dm)",
12602
+ mode: "minor",
12603
+ rootShift: 1,
12604
+ moodId: "mood_dark",
12605
+ description: "\u6050\u308D\u3057\u3044\u3001\u6697\u95C7\u3001\u5632\u308B\u3088\u3046\u306A\u3001\u4E0D\u6C17\u5473\uFF08\u5B30\u30A4\u77ED\u8ABF\u3068\u540C\u97F3\uFF09"
12606
+ },
12607
+ key_Bm: {
12608
+ id: "key_Bm",
12609
+ name: "Bm",
12610
+ label: "\u30ED\u77ED\u8ABF (Bm)",
12611
+ mode: "minor",
12612
+ rootShift: 2,
12613
+ moodId: "mood_melancholy",
12614
+ description: "\u5B64\u72EC\u306A\u3001\u6182\u9B31\u306A\u3001\u5FCD\u8010\u3001\u9759\u304B\u306A\u8AE6\u5FF5"
12615
+ },
12616
+ key_Cm: {
12617
+ id: "key_Cm",
12618
+ name: "Cm",
12619
+ label: "\u30CF\u77ED\u8ABF (Cm)",
12620
+ mode: "minor",
12621
+ rootShift: 3,
12622
+ moodId: "mood_plaintive",
12623
+ description: "\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u306A\u3001\u604B\u308F\u305A\u3089\u3044\u306E\u3001\u60B2\u5287\u7684"
12624
+ },
12625
+ key_Csm: {
12626
+ id: "key_Csm",
12627
+ name: "C\u266Fm",
12628
+ label: "\u5B30\u30CF\u77ED\u8ABF (C\u266Fm)",
12629
+ mode: "minor",
12630
+ rootShift: 4,
12631
+ moodId: "mood_melancholy",
12632
+ description: "\u843D\u80C6\u3001\u6CE3\u304D\u53EB\u3093\u3060\u3001\u60B2\u6D99\u306E\u3001\u6DF1\u3044\u5606\u304D"
12633
+ },
12634
+ key_Dm: {
12635
+ id: "key_Dm",
12636
+ name: "Dm",
12637
+ label: "\u30CB\u77ED\u8ABF (Dm)",
12638
+ mode: "minor",
12639
+ rootShift: 5,
12640
+ moodId: "mood_solemn",
12641
+ description: "\u53B3\u7C9B\u306A\u3001\u656C\u8654\u306A\u3001\u601D\u7D22\u7684\u306A\u3001\u91CD\u539A\u306A\u7948\u308A"
12642
+ },
12643
+ key_Ebm: {
12644
+ id: "key_Ebm",
12645
+ name: "E\u266Dm",
12646
+ label: "\u5909\u30DB\u77ED\u8ABF (E\u266Dm)",
12647
+ mode: "minor",
12648
+ rootShift: 6,
12649
+ moodId: "mood_anxious",
12650
+ description: "\u6DF1\u3044\u82E6\u60A9\u3001\u5B9F\u5B58\u7684\u306A\u4E0D\u5B89\u3001\u6226\u6144\uFF08\u5B30\u30CB\u77ED\u8ABF\u3068\u540C\u97F3\uFF09"
12651
+ },
12652
+ key_Em: {
12653
+ id: "key_Em",
12654
+ name: "Em",
12655
+ label: "\u30DB\u77ED\u8ABF (Em)",
12656
+ mode: "minor",
12657
+ rootShift: -5,
12658
+ moodId: "mood_plaintive",
12659
+ description: "\u5F31\u3005\u3057\u3044\u3001\u306A\u307E\u3081\u304B\u3057\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044\u3001\u7E4A\u7D30"
12660
+ },
12661
+ key_Fm: {
12662
+ id: "key_Fm",
12663
+ name: "Fm",
12664
+ label: "\u30D8\u77ED\u8ABF (Fm)",
12665
+ mode: "minor",
12666
+ rootShift: -4,
12667
+ moodId: "mood_plaintive",
12668
+ description: "\u307C\u3093\u3084\u308A\u3057\u305F\u3001\u7269\u60B2\u3057\u3044\u3001\u3057\u3081\u3084\u304B\u306A\u3001\u846C\u9001"
12669
+ },
12670
+ key_Fsm: {
12671
+ id: "key_Fsm",
12672
+ name: "F\u266Fm",
12673
+ label: "\u5B30\u30D8\u77ED\u8ABF (F\u266Fm)",
12674
+ mode: "minor",
12675
+ rootShift: -3,
12676
+ moodId: "mood_anxious",
12677
+ description: "\u9670\u6C17\u306A\u3001\u6FC0\u3057\u3044\u61A4\u308A\u3001\u6697\u3044\u60C5\u5FF5"
12678
+ },
12679
+ key_Gm: {
12680
+ id: "key_Gm",
12681
+ name: "Gm",
12682
+ label: "\u30C8\u77ED\u8ABF (Gm)",
12683
+ mode: "minor",
12684
+ rootShift: -2,
12685
+ moodId: "mood_anxious",
12686
+ description: "\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u3084\u308B\u305B\u306A\u3055\u3001\u60B2\u75DB\u306A\u53EB\u3073"
12687
+ },
12688
+ key_Abm: {
12689
+ id: "key_Abm",
12690
+ name: "A\u266Dm",
12691
+ label: "\u5909\u30A4\u77ED\u8ABF (A\u266Dm)",
12692
+ mode: "minor",
12693
+ rootShift: -1,
12694
+ moodId: "mood_anxious",
12695
+ description: "\u4E0D\u670D\u306A\u3001\u5606\u304D\u306E\u3001\u6CE3\u304D\u53EB\u3093\u3060\uFF08\u5B30\u30C8\u77ED\u8ABF\u3068\u540C\u97F3\uFF09"
12696
+ }
12697
+ };
12698
+ var COMPOSE_MOOD_GROUPS = [
12531
12699
  {
12532
- grid: [12, 13, 14, 15],
12533
- drums: [
12534
- DRUM_KEYS.acousticSnare,
12535
- DRUM_KEYS.acousticSnare,
12536
- DRUM_KEYS.acousticSnare,
12537
- DRUM_KEYS.acousticSnare
12538
- ]
12700
+ id: "mood_happy",
12701
+ label: "\u559C\u3070\u3057\u3044\u30FB\u967D\u6C17\u306A\u66F2",
12702
+ description: "\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7267\u6B4C\u7684\u3001\u611B\u306E\u544A\u767D\u3001\u98A8\u5909\u308F\u308A\u3067\u967D\u6C17",
12703
+ keyIds: ["key_C", "key_A", "key_Bb"]
12539
12704
  },
12540
- // タムで降りる
12541
12705
  {
12542
- grid: [8, 10, 12, 14],
12543
- drums: [
12544
- DRUM_KEYS.highTom,
12545
- DRUM_KEYS.highTom,
12546
- DRUM_KEYS.lowMidTom,
12547
- DRUM_KEYS.lowTom
12548
- ]
12706
+ id: "mood_triumphant",
12707
+ label: "\u52DD\u5229\u30FB\u529B\u5F37\u3044\u66F2",
12708
+ description: "\u610F\u6C17\u63DA\u3005\u3001\u52DD\u5229\u306E\u558A\u58F0\u3001\u56F0\u96E3\u306E\u6253\u7834\u3001\u5B89\u5835\u306E\u305F\u3081\u606F",
12709
+ keyIds: ["key_D", "key_Gb"]
12549
12710
  },
12550
- // スネア → タム
12551
12711
  {
12552
- grid: [8, 9, 10, 11, 12, 14],
12553
- drums: [
12554
- DRUM_KEYS.acousticSnare,
12555
- DRUM_KEYS.acousticSnare,
12556
- DRUM_KEYS.highTom,
12557
- DRUM_KEYS.highTom,
12558
- DRUM_KEYS.lowMidTom,
12559
- DRUM_KEYS.lowTom
12560
- ]
12712
+ id: "mood_fierce",
12713
+ label: "\u6FC0\u3057\u3044\u30FB\u8352\u3005\u3057\u3044\u66F2",
12714
+ description: "\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u6012\u308A\u72C2\u3063\u305F\u6C17\u6027\u306E\u8352\u3055\u3001\u3069\u304E\u3064\u304F\u731B\u70C8",
12715
+ keyIds: ["key_E", "key_F", "key_B"]
12561
12716
  },
12562
- // 短く締める
12563
12717
  {
12564
- grid: [14, 15],
12565
- drums: [DRUM_KEYS.acousticSnare, DRUM_KEYS.acousticSnare]
12718
+ id: "mood_solemn",
12719
+ label: "\u53B3\u7C9B\u30FB\u5E7B\u60F3\u7684\u306A\u66F2",
12720
+ description: "\u53B3\u7C9B\u3001\u5D07\u9AD8\u3001\u5E7B\u60F3\u3001\u656C\u8654\u3001\u601D\u7D22\u7684\u3001\u67D4\u3089\u304B\u306A\u7269\u60B2\u3057\u3055",
12721
+ keyIds: ["key_G", "key_Dm", "key_Am"]
12566
12722
  },
12567
- // 16分の連打から解放
12568
12723
  {
12569
- grid: [8, 9, 10, 11, 12, 13, 14, 15],
12570
- drums: [
12571
- DRUM_KEYS.acousticSnare,
12572
- DRUM_KEYS.acousticSnare,
12573
- DRUM_KEYS.acousticSnare,
12574
- DRUM_KEYS.acousticSnare,
12575
- DRUM_KEYS.highTom,
12576
- DRUM_KEYS.highTom,
12577
- DRUM_KEYS.lowMidTom,
12578
- DRUM_KEYS.lowTom
12579
- ]
12724
+ id: "mood_plaintive",
12725
+ label: "\u7269\u60B2\u3057\u3044\u30FB\u54C0\u6101\u306E\u66F2",
12726
+ description: "\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u3001\u604B\u308F\u305A\u3089\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044\u3001\u3057\u3081\u3084\u304B\u306A\u60B2\u54C0",
12727
+ keyIds: ["key_Cm", "key_Em", "key_Fm"]
12728
+ },
12729
+ {
12730
+ id: "mood_melancholy",
12731
+ label: "\u6182\u9B31\u30FB\u5B64\u72EC\u306A\u66F2",
12732
+ description: "\u60B2\u3057\u307F\u3001\u6182\u9B31\u3001\u5B64\u72EC\u3001\u5FCD\u8010\u3001\u843D\u80C6\u3001\u60B2\u6D99\u306E\u5606\u304D",
12733
+ keyIds: ["key_Db", "key_Bm", "key_Csm"]
12734
+ },
12735
+ {
12736
+ id: "mood_anxious",
12737
+ label: "\u4E0D\u5B89\u30FB\u82E6\u60A9\u306A\u66F2",
12738
+ description: "\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u6DF1\u3044\u82E6\u60A9\u3001\u5B9F\u5B58\u7684\u4E0D\u5B89\u3001\u9670\u6C17\u306A\u61A4\u308A\u3001\u5606\u304D",
12739
+ keyIds: ["key_Gm", "key_Ebm", "key_Fsm", "key_Abm"]
12580
12740
  },
12581
- // 3連のタム回し(16分格子の外)
12582
12741
  {
12583
- grid: [12, 13.33, 14.67],
12584
- drums: [DRUM_KEYS.highTom, DRUM_KEYS.lowMidTom, DRUM_KEYS.lowTom]
12742
+ id: "mood_dark",
12743
+ label: "\u6697\u95C7\u30FB\u91CD\u539A\u306A\u66F2",
12744
+ description: "\u53B3\u3057\u3044\u611B\u3001\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6050\u308D\u3057\u3044\u6697\u95C7\u3001\u5632\u308A",
12745
+ keyIds: ["key_Eb", "key_Ab", "key_Bbm"]
12585
12746
  }
12586
12747
  ];
12587
- var pick = (items, rnd) => items[Math.floor(rnd() * items.length)];
12588
- var shuffleHats = (stepsPerBar, level) => {
12589
- const beat = stepsPerBar / 4;
12590
- const triplet = beat / 3;
12591
- const out = [];
12592
- for (let b = 0; b < 4; b++) {
12593
- out.push(b * beat);
12594
- out.push(b * beat + triplet * 2);
12595
- if (level === 2) out.push(b * beat + triplet);
12748
+ var MAJOR_KEY_IDS = Object.values(COMPOSE_KEYS).filter((k) => k.mode === "major").map((k) => k.id);
12749
+ var MINOR_KEY_IDS = Object.values(COMPOSE_KEYS).filter((k) => k.mode === "minor").map((k) => k.id);
12750
+ var pickItem = (list, rnd) => list[Math.floor(rnd() * list.length)];
12751
+ var resolveComposeKey = (choice, rnd = Math.random) => {
12752
+ const c = (choice ?? "").trim() || "any";
12753
+ if (COMPOSE_KEYS[c]) {
12754
+ const target2 = COMPOSE_KEYS[c];
12755
+ const mood2 = COMPOSE_MOOD_GROUPS.find((m) => m.id === target2.moodId);
12756
+ return {
12757
+ mode: target2.mode,
12758
+ rootShift: target2.rootShift,
12759
+ keyName: target2.name,
12760
+ keyLabel: target2.label,
12761
+ moodLabel: mood2?.label,
12762
+ description: target2.description
12763
+ };
12596
12764
  }
12597
- return out;
12598
- };
12599
- var composeDrumPattern = (options) => {
12600
- const { bars, stepsPerBar, rnd } = options;
12601
- const style = options.style ?? pick(
12602
- ["eight", "sixteen", "four", "shuffle", "ballad", "rock"],
12603
- rnd
12604
- );
12605
- const toStep = (g) => Math.round(g * stepsPerBar / 16) % stepsPerBar;
12606
- const fillA = pick(FILLS, rnd);
12607
- const fillB = pick(
12608
- FILLS.filter((f) => f !== fillA),
12609
- rnd
12610
- );
12611
- const openTail = rnd() < 0.6;
12612
- const useClap = style === "four" && rnd() < 0.5;
12613
- const levels = options.levels;
12614
- const crashSet = new Set(options.crashBars ?? [1, 9, 13]);
12615
- const fillSet = new Set(
12616
- options.fillBars ?? [4, 8, 12, bars - 1].filter((b) => b > 0)
12617
- );
12618
- const sectionLevel = (bar) => {
12619
- if (levels) return levels[bar - 1] ?? 1;
12620
- if (bar <= 4) return 0;
12621
- if (bar <= 8) return 1;
12622
- if (bar <= 12) return 2;
12623
- return 1;
12624
- };
12625
- const barPatterns = [];
12626
- for (let bar = 1; bar <= bars; bar++) {
12627
- const level = sectionLevel(bar);
12628
- const isLast = bar === bars;
12629
- const isFill = !isLast && fillSet.has(bar);
12630
- const hits = [];
12631
- if (crashSet.has(bar) || isLast)
12632
- hits.push({
12633
- step: 0,
12634
- pitch: DRUM_KEYS.crashCymbal1,
12635
- velocity: bar === 9 || isLast ? 1 : 0.85
12636
- });
12637
- if (isLast) {
12638
- hits.push({ step: 0, pitch: DRUM_KEYS.bassDrum1, velocity: 1 });
12639
- hits.sort((a, b) => a.step - b.step || a.pitch - b.pitch);
12640
- barPatterns.push(hits);
12641
- continue;
12642
- }
12643
- for (const g of KICKS[style][level])
12644
- hits.push({
12645
- step: toStep(g),
12646
- pitch: DRUM_KEYS.bassDrum1,
12647
- velocity: g === 0 ? 1 : 0.88
12648
- });
12649
- const snarePitch = style === "ballad" && level === 0 ? DRUM_KEYS.sideStick : DRUM_KEYS.acousticSnare;
12650
- for (const g of SNARES[style][level]) {
12651
- hits.push({ step: toStep(g), pitch: snarePitch, velocity: 0.95 });
12652
- if (useClap)
12653
- hits.push({
12654
- step: toStep(g),
12655
- pitch: DRUM_KEYS.handClap,
12656
- velocity: 0.7
12657
- });
12658
- }
12659
- if (level >= 1)
12660
- for (const g of GHOSTS[style])
12661
- hits.push({
12662
- step: toStep(g),
12663
- pitch: DRUM_KEYS.acousticSnare,
12664
- velocity: 0.3
12665
- });
12666
- const hatSteps = style === "shuffle" ? shuffleHats(stepsPerBar, level) : HIHATS[style][level].map(toStep);
12667
- const hatPitch = level === 2 && style !== "four" ? DRUM_KEYS.rideCymbal1 : DRUM_KEYS.closedHihat;
12668
- for (const step of hatSteps) {
12669
- if (isFill && step >= stepsPerBar / 2) continue;
12670
- const isDownbeat = step % (stepsPerBar / 4) === 0;
12671
- const isTail = openTail && step === toStep(14) && !isFill;
12672
- hits.push({
12673
- step,
12674
- pitch: isTail ? DRUM_KEYS.openHihat : hatPitch,
12675
- velocity: isTail ? 0.8 : isDownbeat ? 0.75 : 0.5
12676
- });
12677
- }
12678
- if (isFill) {
12679
- const fill = bar % 8 === 0 ? fillB : fillA;
12680
- fill.grid.forEach((g, i2) => {
12681
- hits.push({
12682
- step: toStep(g),
12683
- pitch: fill.drums[i2] ?? DRUM_KEYS.acousticSnare,
12684
- // 追い込みで少しずつ強くする
12685
- velocity: 0.7 + 0.28 * i2 / Math.max(1, fill.grid.length - 1)
12686
- });
12687
- });
12688
- }
12689
- hits.sort((a, b) => a.step - b.step || a.pitch - b.pitch);
12690
- barPatterns.push(hits);
12691
- }
12692
- const pattern = [];
12693
- let runStart = 1;
12694
- for (let bar = 1; bar <= bars; bar++) {
12695
- const same = bar < bars && JSON.stringify(barPatterns[bar]) === JSON.stringify(barPatterns[bar - 1]);
12696
- if (same) continue;
12697
- const instruction = {
12698
- ranges: [[runStart, bar]],
12699
- patternBars: 1,
12700
- pattern: barPatterns[runStart - 1]
12765
+ const mood = COMPOSE_MOOD_GROUPS.find((m) => m.id === c);
12766
+ if (mood) {
12767
+ const pickedKeyId2 = pickItem(mood.keyIds, rnd);
12768
+ const target2 = COMPOSE_KEYS[pickedKeyId2];
12769
+ return {
12770
+ mode: target2.mode,
12771
+ rootShift: target2.rootShift,
12772
+ keyName: target2.name,
12773
+ keyLabel: target2.label,
12774
+ moodLabel: mood.label,
12775
+ description: target2.description
12776
+ };
12777
+ }
12778
+ if (c === "major") {
12779
+ const pickedKeyId2 = pickItem(MAJOR_KEY_IDS, rnd);
12780
+ const target2 = COMPOSE_KEYS[pickedKeyId2];
12781
+ const targetMood2 = COMPOSE_MOOD_GROUPS.find((m) => m.id === target2.moodId);
12782
+ return {
12783
+ mode: "major",
12784
+ rootShift: target2.rootShift,
12785
+ keyName: target2.name,
12786
+ keyLabel: target2.label,
12787
+ moodLabel: targetMood2?.label,
12788
+ description: target2.description
12701
12789
  };
12702
- pattern.push(instruction);
12703
- runStart = bar + 1;
12704
12790
  }
12705
- const variety = new Set(barPatterns.map((p) => JSON.stringify(p))).size;
12791
+ if (c === "minor") {
12792
+ const pickedKeyId2 = pickItem(MINOR_KEY_IDS, rnd);
12793
+ const target2 = COMPOSE_KEYS[pickedKeyId2];
12794
+ const targetMood2 = COMPOSE_MOOD_GROUPS.find((m) => m.id === target2.moodId);
12795
+ return {
12796
+ mode: "minor",
12797
+ rootShift: target2.rootShift,
12798
+ keyName: target2.name,
12799
+ keyLabel: target2.label,
12800
+ moodLabel: targetMood2?.label,
12801
+ description: target2.description
12802
+ };
12803
+ }
12804
+ const allKeyIds = Object.keys(COMPOSE_KEYS);
12805
+ const pickedKeyId = pickItem(allKeyIds, rnd);
12806
+ const target = COMPOSE_KEYS[pickedKeyId];
12807
+ const targetMood = COMPOSE_MOOD_GROUPS.find((m) => m.id === target.moodId);
12706
12808
  return {
12707
- style,
12708
- variety,
12709
- def: { label: `\u4F5C\u66F2\uFF08${DRUM_STYLE_LABELS[style]}\uFF09`, pattern }
12710
- };
12809
+ mode: void 0,
12810
+ // コード進行の長短制約は掛けず従来通りランダム
12811
+ rootShift: target.rootShift,
12812
+ keyName: target.name,
12813
+ keyLabel: target.label,
12814
+ moodLabel: targetMood?.label,
12815
+ description: target.description
12816
+ };
12817
+ };
12818
+ var getComposeKeyDescription = (choice) => {
12819
+ if (!choice || choice === "any") return "\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059";
12820
+ if (choice === "major") return "12\u306E\u9577\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059";
12821
+ if (choice === "minor") return "12\u306E\u77ED\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059";
12822
+ const mood = COMPOSE_MOOD_GROUPS.find((m) => m.id === choice);
12823
+ if (mood) return `${mood.label}\uFF1A${mood.description}`;
12824
+ const target = COMPOSE_KEYS[choice];
12825
+ if (target) return `${target.label}\uFF1A${target.description}`;
12826
+ return "";
12711
12827
  };
12712
12828
 
12713
12829
  // src/compose-metrics.ts
@@ -13139,7 +13255,6 @@ var transposeChordName = (chord, shift) => {
13139
13255
  return names[((pc + normShift) % 12 + 12) % 12];
13140
13256
  });
13141
13257
  };
13142
- var ROOT_SHIFTS = [0, 1, 2, 3, 4, 5, 6, -1, -2, -3, -4, -5];
13143
13258
  var BPM_CHOICES = [
13144
13259
  112,
13145
13260
  120,
@@ -13761,7 +13876,7 @@ var MOTIF_ARCHETYPES = [
13761
13876
  [0, 5, 4, 2, 0]
13762
13877
  // 6度跳躍からの大きな下降
13763
13878
  ];
13764
- var pick2 = (items, rnd) => items[Math.floor(rnd() * items.length)];
13879
+ var pick = (items, rnd) => items[Math.floor(rnd() * items.length)];
13765
13880
  var durationEntropy = (durations) => {
13766
13881
  if (durations.length === 0) return 0;
13767
13882
  const counts = /* @__PURE__ */ new Map();
@@ -13829,7 +13944,7 @@ var leapTarget = (from, tones, rnd) => {
13829
13944
  ...octaves,
13830
13945
  ...near.slice(0, Math.max(2, Math.ceil(near.length / 2)))
13831
13946
  ];
13832
- return pick2(weighted.length > 0 ? weighted : candidates, rnd);
13947
+ return pick(weighted.length > 0 ? weighted : candidates, rnd);
13833
13948
  };
13834
13949
  var landOn = (degrees, scaleIndex) => {
13835
13950
  if (degrees.length === 0) return;
@@ -13858,7 +13973,7 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
13858
13973
  const noteCount = slots.length;
13859
13974
  const out = [];
13860
13975
  if (role === "motif" || role === "sequence" || role === "climax" || role === "answer") {
13861
- const shift = (role === "sequence" ? pick2([-2, -1, 1, 2], rnd) : role === "climax" ? 5 : 0) + repeatShift;
13976
+ const shift = (role === "sequence" ? pick([-2, -1, 1, 2], rnd) : role === "climax" ? 5 : 0) + repeatShift;
13862
13977
  if (style.pentatonicMotif) {
13863
13978
  const startPenta = degreeToPenta(startDegree);
13864
13979
  for (let i2 = 0; i2 < noteCount; i2++)
@@ -13924,7 +14039,7 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
13924
14039
  }
13925
14040
  return out;
13926
14041
  }
13927
- const span = pick2([2, 3, 4], rnd);
14042
+ const span = pick([2, 3, 4], rnd);
13928
14043
  const dir = style.stepShape === "descend" ? -1 : style.stepShape === "ascend" ? 1 : rnd() < 0.5 ? 1 : -1;
13929
14044
  for (let i2 = 0; i2 < noteCount; i2++) {
13930
14045
  const t = noteCount === 1 ? 0 : i2 / (noteCount - 1);
@@ -14152,7 +14267,7 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
14152
14267
  lastAltered = i2;
14153
14268
  }
14154
14269
  };
14155
- var draw = (options, rnd) => {
14270
+ var draw = (options, resolvedKey, rnd) => {
14156
14271
  const stepsPerBar = options.stepsPerBar;
14157
14272
  const edo = options.edo === 31 ? 31 : 12;
14158
14273
  const scaleStep = (v) => Math.max(1, Math.round(Math.abs(v) * stepsPerBar / BASE_STEPS_PER_BAR)) * Math.sign(v);
@@ -14176,12 +14291,12 @@ var draw = (options, rnd) => {
14176
14291
  const sectionPlan = buildSectionPlan(options.sections ?? DEFAULT_SECTIONS);
14177
14292
  const totalBars = sectionPlan.reduce((sum, s) => sum + s.bars, 0);
14178
14293
  if (rnd() < 0.35) {
14179
- const modType = pick2(
14294
+ const modType = pick(
14180
14295
  ["chorus_up", "chorus_up", "prechorus_down"],
14181
14296
  rnd
14182
14297
  );
14183
14298
  if (modType === "chorus_up") {
14184
- const shift = pick2([1, 1, 2], rnd);
14299
+ const shift = pick([1, 1, 2], rnd);
14185
14300
  let lastChorusIdx = -1;
14186
14301
  for (let i2 = 0; i2 < sectionPlan.length; i2++) {
14187
14302
  if (sectionPlan[i2].kind === "chorus") lastChorusIdx = i2;
@@ -14192,7 +14307,7 @@ var draw = (options, rnd) => {
14192
14307
  }
14193
14308
  }
14194
14309
  } else {
14195
- const shift = pick2([-2, 3], rnd);
14310
+ const shift = pick([-2, 3], rnd);
14196
14311
  for (const s of sectionPlan) {
14197
14312
  if (s.kind === "prechorus") s.keyShift = shift;
14198
14313
  }
@@ -14204,14 +14319,15 @@ var draw = (options, rnd) => {
14204
14319
  barKeyShift[b] = s.keyShift;
14205
14320
  }
14206
14321
  }
14207
- const progA = pick2(SECTION_A_PROGRESSIONS, rnd);
14322
+ const progAPool = resolvedKey.mode === "major" ? SECTION_A_PROGRESSIONS.filter((p) => !p[0].startsWith("Am")) : resolvedKey.mode === "minor" ? SECTION_A_PROGRESSIONS.filter((p) => p[0].startsWith("Am")) : SECTION_A_PROGRESSIONS;
14323
+ const progA = pick(progAPool, rnd);
14208
14324
  const progBPool = SECTION_B_PROGRESSIONS.filter(
14209
14325
  (p) => p.join("|") !== progA.join("|")
14210
14326
  );
14211
- const progB = pick2(progBPool, rnd);
14327
+ const progB = pick(progBPool, rnd);
14212
14328
  const tonic = progA[0].startsWith("Am") ? "Am" : "C";
14213
- const progHalf = pick2(SECTION_A2_DERIVATIONS, rnd)(progA);
14214
- const progFull = pick2(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
14329
+ const progHalf = pick(SECTION_A2_DERIVATIONS, rnd)(progA);
14330
+ const progFull = pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
14215
14331
  const progression = [];
14216
14332
  for (const section of sectionPlan) {
14217
14333
  const base = section.spec.progression === "b" ? progB : progA;
@@ -14229,9 +14345,9 @@ var draw = (options, rnd) => {
14229
14345
  }
14230
14346
  progression.length = totalBars;
14231
14347
  const chordProgression = progression.map((chord, bar) => transposeChordName(chord, barKeyShift[bar])).join("|");
14232
- const chordPattern = pick2(CHORD_PATTERNS, rnd);
14233
- const rootShift = pick2(ROOT_SHIFTS, rnd);
14234
- const bpm = pick2(BPM_CHOICES, rnd);
14348
+ const chordPattern = pick(CHORD_PATTERNS, rnd);
14349
+ const rootShift = resolvedKey.rootShift;
14350
+ const bpm = pick(BPM_CHOICES, rnd);
14235
14351
  const sourceOf = (kind) => kind === "chorus" || kind === "interlude" ? "b" : kind === "prechorus" ? "a2" : "a";
14236
14352
  const units2 = [];
14237
14353
  for (const section of sectionPlan) {
@@ -14280,19 +14396,19 @@ var draw = (options, rnd) => {
14280
14396
  return null;
14281
14397
  };
14282
14398
  const style = {
14283
- groove: pick2(["eighth", "sixteenth"], rnd),
14284
- arcPeriod: pick2([4, 8, 8, 16], rnd),
14285
- arcPhase: pick2([0, 1, 2], rnd),
14399
+ groove: pick(["eighth", "sixteenth"], rnd),
14400
+ arcPeriod: pick([4, 8, 8, 16], rnd),
14401
+ arcPhase: pick([0, 1, 2], rnd),
14286
14402
  arcAmp: 3 + rnd() * 4,
14287
14403
  octaveAffinity: 0.14 + rnd() * 0.2,
14288
14404
  pentatonicMotif: rnd() < 0.55,
14289
- runShape: pick2(["scale", "turn", "broken", "zigzag"], rnd),
14290
- stepShape: pick2(
14405
+ runShape: pick(["scale", "turn", "broken", "zigzag"], rnd),
14406
+ stepShape: pick(
14291
14407
  ["arch", "valley", "ascend", "descend", "wave", "pivot"],
14292
14408
  rnd
14293
14409
  ),
14294
- holdShape: pick2(["long", "third", "neighbor"], rnd),
14295
- cadenceShape: pick2(
14410
+ holdShape: pick(["long", "third", "neighbor"], rnd),
14411
+ cadenceShape: pick(
14296
14412
  ["descend", "five-three-one", "leap-up", "hold-tonic"],
14297
14413
  rnd
14298
14414
  ),
@@ -14302,7 +14418,7 @@ var draw = (options, rnd) => {
14302
14418
  // 全曲に掛けると「どの曲も同じ味付け」になる)。
14303
14419
  chromaticAffinity: rnd() < 0.25 ? 0 : 0.15 + rnd() * 0.45,
14304
14420
  barHeadWeight: rnd() < 0.65 ? 3 : 2,
14305
- bassStyle: pick2(
14421
+ bassStyle: pick(
14306
14422
  [
14307
14423
  "quarter",
14308
14424
  "alternate",
@@ -14323,8 +14439,8 @@ var draw = (options, rnd) => {
14323
14439
  // パッド・保続音・付点2分は「置いただけの音」になりやすく、実際
14324
14440
  // サブメロの音の38%が全音符1つ(1.5音/小節)まで薄くなっていた。
14325
14441
  // それらは終止の小節でだけ使う逃げ道に降格する。
14326
- subStyle: pick2(["harmony", "harmony", "counter"], rnd),
14327
- subInterval: pick2([3, 4, 8, 9], rnd)
14442
+ subStyle: pick(["harmony", "harmony", "counter"], rnd),
14443
+ subInterval: pick([3, 4, 8, 9], rnd)
14328
14444
  };
14329
14445
  const motifPool = groovyCells(MOTIF_CELLS, style.groove, rnd);
14330
14446
  const targetNotesPerBar = 5.5 + rnd() * 2.5;
@@ -14341,9 +14457,9 @@ var draw = (options, rnd) => {
14341
14457
  };
14342
14458
  const cellDistance = (c) => Math.abs(cellNotes(c) - targetNotesPerBar) + Math.abs(cellRest(c) - targetRestRatio) * 8;
14343
14459
  const pickCell = (pool) => {
14344
- let best = pick2(pool, rnd);
14460
+ let best = pick(pool, rnd);
14345
14461
  for (let i2 = 0; i2 < 2; i2++) {
14346
- const c = pick2(pool, rnd);
14462
+ const c = pick(pool, rnd);
14347
14463
  if (cellDistance(c) < cellDistance(best)) best = c;
14348
14464
  }
14349
14465
  return best;
@@ -14366,7 +14482,7 @@ var draw = (options, rnd) => {
14366
14482
  const motifBVar = pickCell(
14367
14483
  motifPool.filter((c) => c !== motifB && c !== motifB2)
14368
14484
  );
14369
- const buildUpCell = pick2(
14485
+ const buildUpCell = pick(
14370
14486
  [
14371
14487
  {
14372
14488
  value: [EIGHTH, EIGHTH, EIGHTH, EIGHTH, QUARTER, -QUARTER],
@@ -14387,7 +14503,7 @@ var draw = (options, rnd) => {
14387
14503
  ],
14388
14504
  rnd
14389
14505
  );
14390
- const counterRhythm = pick2(
14506
+ const counterRhythm = pick(
14391
14507
  groovyCells(
14392
14508
  RHYTHM_CELLS.filter((c) => c.density === "medium"),
14393
14509
  style.groove,
@@ -14395,7 +14511,7 @@ var draw = (options, rnd) => {
14395
14511
  ),
14396
14512
  rnd
14397
14513
  ).value;
14398
- const breathCell = pick2(
14514
+ const breathCell = pick(
14399
14515
  RHYTHM_CELLS.filter(
14400
14516
  (c) => c.density === "sparse" && c.value.some((v) => v < 0)
14401
14517
  ),
@@ -14432,14 +14548,14 @@ var draw = (options, rnd) => {
14432
14548
  barRhythms.push(scaleCell(cell.value));
14433
14549
  }
14434
14550
  const motifNoteCount = motifCell.value.filter((v) => v > 0).length + motifCell2.value.filter((v) => v > 0).length;
14435
- const archetype = pick2(MOTIF_ARCHETYPES, rnd);
14551
+ const archetype = pick(MOTIF_ARCHETYPES, rnd);
14436
14552
  const motifContour = [];
14437
14553
  let baseDegree = 0;
14438
14554
  for (let i2 = 0; i2 < motifNoteCount; i2++) {
14439
14555
  const idx = i2 % archetype.length;
14440
14556
  if (i2 > 0 && idx === 0) {
14441
14557
  const lastVal = motifContour[i2 - 1];
14442
- const connectShift = pick2([-1, 0, 1], rnd);
14558
+ const connectShift = pick([-1, 0, 1], rnd);
14443
14559
  baseDegree = lastVal + connectShift - archetype[0];
14444
14560
  }
14445
14561
  motifContour.push(baseDegree + archetype[idx]);
@@ -14457,12 +14573,12 @@ var draw = (options, rnd) => {
14457
14573
  let intervals = 0;
14458
14574
  let chromaticNotes = 0;
14459
14575
  let sungBars = 0;
14460
- let prevSemi = pick2([60, 64, 65, 67, 69, 72, 74, 76], rnd);
14576
+ let prevSemi = pick([60, 64, 65, 67, 69, 72, 74, 76], rnd);
14461
14577
  const pickFigure = (gapSteps, scale) => {
14462
14578
  const fits = ANSWER_FIGURES.filter(
14463
14579
  (f) => f.reduce((sum, v) => sum + scale(Math.abs(v)), 0) <= gapSteps
14464
14580
  );
14465
- return fits.length === 0 ? null : pick2(fits, rnd);
14581
+ return fits.length === 0 ? null : pick(fits, rnd);
14466
14582
  };
14467
14583
  const melodyGaps = (slots) => {
14468
14584
  const gaps = [];
@@ -14503,7 +14619,7 @@ var draw = (options, rnd) => {
14503
14619
  ).semi;
14504
14620
  const contourOffset = barInUnit(bar) === 0 ? 0 : barRhythms[bar - 1].filter((v) => v > 0).length;
14505
14621
  const prevRole = bar > 0 ? barRoles[bar - 1] : null;
14506
- const repeatShift = role === "motif" && (prevRole === "motif" || prevRole === "sequence" || prevRole === "climax") ? pick2([-2, -1, 1, 2], rnd) : 0;
14622
+ const repeatShift = role === "motif" && (prevRole === "motif" || prevRole === "sequence" || prevRole === "climax") ? pick([-2, -1, 1, 2], rnd) : 0;
14507
14623
  const degrees = barDegrees(
14508
14624
  role,
14509
14625
  slots,
@@ -14799,6 +14915,9 @@ var draw = (options, rnd) => {
14799
14915
  chordProgression,
14800
14916
  chordPattern,
14801
14917
  rootShift,
14918
+ keyName: resolvedKey.keyName,
14919
+ keyLabel: resolvedKey.keyLabel,
14920
+ moodLabel: resolvedKey.moodLabel,
14802
14921
  bpm,
14803
14922
  sections: sectionPlan,
14804
14923
  bars: totalBars,
@@ -14913,12 +15032,13 @@ var composeSong = (options) => {
14913
15032
  const rnd = options.random ?? Math.random;
14914
15033
  const recent = options.recent ?? [];
14915
15034
  const count = Math.max(1, options.drawCount ?? DRAW_COUNT);
15035
+ const resolvedKey = resolveComposeKey(options.baseKey, rnd);
14916
15036
  let best = null;
14917
15037
  let bestScore = Number.NEGATIVE_INFINITY;
14918
15038
  let bestIsValid = false;
14919
15039
  let rejected = 0;
14920
15040
  for (let attempt = 1; attempt <= count; attempt++) {
14921
- const d = draw(options, rnd);
15041
+ const d = draw(options, resolvedKey, rnd);
14922
15042
  const { stats, ok } = evaluate(d, recent);
14923
15043
  if (!ok) rejected++;
14924
15044
  const better = ok === bestIsValid ? stats.score > bestScore : ok;
@@ -14928,10 +15048,13 @@ var composeSong = (options) => {
14928
15048
  best = {
14929
15049
  // ドラムは勝った候補にだけ後から付ける(メロディに依存しないので
14930
15050
  // 候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
14931
- drums: null,
15051
+ drum: "",
14932
15052
  chordProgression: d.chordProgression,
14933
15053
  chordPattern: d.chordPattern,
14934
15054
  rootShift: d.rootShift,
15055
+ keyName: d.keyName,
15056
+ keyLabel: d.keyLabel,
15057
+ moodLabel: d.moodLabel,
14935
15058
  bpm: d.bpm,
14936
15059
  sections: d.sections,
14937
15060
  bars: d.bars,
@@ -14944,32 +15067,14 @@ var composeSong = (options) => {
14944
15067
  const result = best;
14945
15068
  result.stats.attempts = count;
14946
15069
  result.stats.rejected = rejected;
14947
- const levels = new Array(result.bars).fill(1);
14948
- const crashBars = [];
14949
- const fillBars = [];
14950
- for (const section of result.sections) {
14951
- for (let i2 = 0; i2 < section.bars; i2++)
14952
- levels[section.startBar + i2] = section.spec.drumLevel;
14953
- crashBars.push(section.startBar + 1);
14954
- const fill = section.startBar + section.bars - 1;
14955
- if (fill > 0) fillBars.push(fill);
14956
- }
14957
- result.drums = composeDrumPattern({
14958
- bars: result.bars,
14959
- stepsPerBar: options.stepsPerBar,
14960
- rnd,
14961
- style: pickDrumStyle(result, rnd),
14962
- levels,
14963
- crashBars,
14964
- fillBars
14965
- });
15070
+ result.drum = pickBuiltinDrum(result, rnd);
14966
15071
  return result;
14967
15072
  };
14968
- var pickDrumStyle = (song, rnd) => {
15073
+ var pickBuiltinDrum = (song, rnd) => {
14969
15074
  const eighth = BASE_STEPS_PER_BAR / 8;
14970
15075
  const short = song.melody.filter((n) => n.durationSteps <= eighth).length / Math.max(1, song.melody.length);
14971
- const pool = song.bpm >= 150 ? ["four", "rock", "sixteen"] : short >= 0.6 ? ["sixteen", "four", "rock"] : song.bpm <= 124 && short < 0.4 ? ["ballad", "eight", "ballad"] : ["eight", "rock", "four", "sixteen"];
14972
- return pick2(pool, rnd);
15076
+ const pool = song.bpm >= 150 ? ["4beat", "dance", "16beat", "disco"] : short >= 0.6 ? ["16beat", "dance", "4beat", "disco"] : song.bpm <= 110 && short < 0.4 ? ["bossa", "8beat"] : ["8beat", "4beat", "16beat"];
15077
+ return pick(pool, rnd);
14973
15078
  };
14974
15079
  var LYRIC_WORDS = [
14975
15080
  "\u3042\u3055",
@@ -15017,7 +15122,7 @@ var composeLyrics = (melody, options) => {
15017
15122
  const quarter = stepsPerBar / 4;
15018
15123
  let buffer = [];
15019
15124
  const nextKana = () => {
15020
- if (buffer.length === 0) buffer = [...pick2(LYRIC_WORDS, rnd)];
15125
+ if (buffer.length === 0) buffer = [...pick(LYRIC_WORDS, rnd)];
15021
15126
  return buffer.shift();
15022
15127
  };
15023
15128
  const out = [];
@@ -15382,6 +15487,56 @@ var buildUI = (target, options) => {
15382
15487
  <span class="dtm-grow"></span>
15383
15488
  <span class="dtm-hint" data-dtm="compose-sections-len"></span>
15384
15489
  </div>
15490
+ <div class="dtm-row ${showCompose ? "" : "dtm-hidden"}" data-dtm="compose-key-row">
15491
+ <span class="dtm-label">\u30D9\u30FC\u30B9\u8ABF</span>
15492
+ <select class="dtm-select" data-dtm="compose-key" title="\u81EA\u52D5\u4F5C\u66F2\u306E\u30D9\u30FC\u30B9\u3068\u306A\u308B\u8ABF\u3084\u96F0\u56F2\u6C17\u3092\u9078\u3073\u307E\u3059">
15493
+ <option value="any" title="\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057</option>
15494
+ <optgroup label="\u57FA\u672C">
15495
+ <option value="major" title="12\u306E\u9577\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u9577\u8ABF</option>
15496
+ <option value="minor" title="12\u306E\u77ED\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u77ED\u8ABF</option>
15497
+ </optgroup>
15498
+ <optgroup label="\u96F0\u56F2\u6C17\u304B\u3089\u9078\u3076\uFF08\u62BD\u9078\uFF09">
15499
+ <option value="mood_happy" title="\u30CF\u9577\u8ABF\u30FB\u30A4\u9577\u8ABF\u30FB\u5909\u30ED\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7267\u6B4C\u7684\u3001\u967D\u6C17\uFF09">\u559C\u3070\u3057\u3044\u30FB\u967D\u6C17\u306A\u66F2</option>
15500
+ <option value="mood_triumphant" title="\u30CB\u9577\u8ABF\u30FB\u5909\u30C8\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u610F\u6C17\u63DA\u3005\u3001\u52DD\u5229\u306E\u558A\u58F0\u3001\u56F0\u96E3\u6253\u7834\u3001\u5B89\u5835\uFF09">\u52DD\u5229\u30FB\u529B\u5F37\u3044\u66F2</option>
15501
+ <option value="mood_fierce" title="\u30DB\u9577\u8ABF\u30FB\u30D8\u9577\u8ABF\u30FB\u30ED\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u6012\u308A\u72C2\u3063\u305F\u8352\u3005\u3057\u3055\u3001\u3069\u304E\u3064\u304F\u731B\u70C8\uFF09">\u6FC0\u3057\u3044\u30FB\u8352\u3005\u3057\u3044\u66F2</option>
15502
+ <option value="mood_solemn" title="\u30C8\u9577\u8ABF\u30FB\u30CB\u77ED\u8ABF\u30FB\u30A4\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u53B3\u7C9B\u3001\u5D07\u9AD8\u3001\u5E7B\u60F3\u3001\u656C\u8654\u3001\u601D\u7D22\u7684\uFF09">\u53B3\u7C9B\u30FB\u5E7B\u60F3\u7684\u306A\u66F2</option>
15503
+ <option value="mood_plaintive" title="\u30CF\u77ED\u8ABF\u30FB\u30DB\u77ED\u8ABF\u30FB\u30D8\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u3001\u604B\u308F\u305A\u3089\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044\u3001\u7269\u60B2\u3057\u3044\u54C0\u6101\uFF09">\u7269\u60B2\u3057\u3044\u30FB\u54C0\u6101\u306E\u66F2</option>
15504
+ <option value="mood_melancholy" title="\u5909\u30CB\u9577\u8ABF\u30FB\u30ED\u77ED\u8ABF\u30FB\u5B30\u30CF\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u60B2\u3057\u307F\u3001\u6182\u9B31\u3001\u5B64\u72EC\u3001\u5FCD\u8010\u3001\u843D\u80C6\u3001\u60B2\u6D99\uFF09">\u6182\u9B31\u30FB\u5B64\u72EC\u306A\u66F2</option>
15505
+ <option value="mood_anxious" title="\u30C8\u77ED\u8ABF\u30FB\u5909\u30DB\u77ED\u8ABF\u30FB\u5B30\u30D8\u77ED\u8ABF\u30FB\u5909\u30A4\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u6DF1\u3044\u82E6\u60A9\u3001\u9670\u6C17\u306A\u61A4\u308A\uFF09">\u4E0D\u5B89\u30FB\u82E6\u60A9\u306A\u66F2</option>
15506
+ <option value="mood_dark" title="\u5909\u30DB\u9577\u8ABF\u30FB\u5909\u30A4\u9577\u8ABF\u30FB\u5909\u30ED\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u53B3\u3057\u3044\u611B\u3001\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6697\u95C7\u3001\u6050\u308D\u3057\u3044\u5632\u308A\uFF09">\u6697\u95C7\u30FB\u91CD\u539A\u306A\u66F2</option>
15507
+ </optgroup>
15508
+ <optgroup label="\u9577\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
15509
+ <option value="key_C" title="\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7D14\u7C8B\u3001\u7D20\u6734\u3001\u51FA\u767A">\u30CF\u9577\u8ABF (C)</option>
15510
+ <option value="key_Db" title="\u60B2\u3057\u307F\u3001\u6182\u9B31\u306A\u3001\u7518\u7F8E\u306A\u611F\u50B7">\u5909\u30CB\u9577\u8ABF (D\u266D)</option>
15511
+ <option value="key_D" title="\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0">\u30CB\u9577\u8ABF (D)</option>
15512
+ <option value="key_Eb" title="\u53B3\u3057\u3044\u3001\u304D\u3064\u3044\u3001\u305D\u308C\u3067\u3044\u3066\u611B\u306B\u6E80\u3061\u305F">\u5909\u30DB\u9577\u8ABF (E\u266D)</option>
15513
+ <option value="key_E" title="\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u8352\u3005\u3057\u3044\u3001\u8F1D\u304B\u3057\u3044\u60C5\u71B1">\u30DB\u9577\u8ABF (E)</option>
15514
+ <option value="key_F" title="\u6012\u308A\u72C2\u3063\u305F\u3001\u6C17\u6027\u306E\u8352\u3044\u3001\u4E00\u6642\u7684\u306A\u60B2\u5606">\u30D8\u9577\u8ABF (F)</option>
15515
+ <option value="key_Gb" title="\u56F0\u96E3\u306E\u6253\u7834\u3001\u5B89\u5835\u306E\u305F\u3081\u606F\u3001\u51F1\u65CB">\u5909\u30C8\u9577\u8ABF (G\u266D)</option>
15516
+ <option value="key_G" title="\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F">\u30C8\u9577\u8ABF (G)</option>
15517
+ <option value="key_Ab" title="\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6DF1\u9060\u306A\u7791\u60F3">\u5909\u30A4\u9577\u8ABF (A\u266D)</option>
15518
+ <option value="key_A" title="\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D">\u30A4\u9577\u8ABF (A)</option>
15519
+ <option value="key_Bb" title="\u559C\u3070\u3057\u3044\u3001\u98A8\u5909\u308F\u308A\u306A\u3001\u967D\u6C17\u306A\u3001\u8EFD\u5FEB">\u5909\u30ED\u9577\u8ABF (B\u266D)</option>
15520
+ <option value="key_B" title="\u3069\u304E\u3064\u3044\u3001\u5F37\u70C8\u306A\u3001\u8352\u3063\u307D\u3044\u3001\u731B\u70C8">\u30ED\u9577\u8ABF (B)</option>
15521
+ </optgroup>
15522
+ <optgroup label="\u77ED\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
15523
+ <option value="key_Am" title="\u67D4\u3089\u304B\u306A\u3001\u7269\u60B2\u3057\u3044\u3001\u656C\u8654\u306A\u3001\u7D20\u6734\u306A\u54C0\u6101">\u30A4\u77ED\u8ABF (Am)</option>
15524
+ <option value="key_Bbm" title="\u6050\u308D\u3057\u3044\u3001\u6697\u95C7\u3001\u5632\u308B\u3088\u3046\u306A\u3001\u4E0D\u6C17\u5473">\u5909\u30ED\u77ED\u8ABF (B\u266Dm)</option>
15525
+ <option value="key_Bm" title="\u5B64\u72EC\u306A\u3001\u6182\u9B31\u306A\u3001\u5FCD\u8010\u3001\u9759\u304B\u306A\u8AE6\u5FF5">\u30ED\u77ED\u8ABF (Bm)</option>
15526
+ <option value="key_Cm" title="\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u306A\u3001\u604B\u308F\u305A\u3089\u3044\u306E\u3001\u60B2\u5287\u7684">\u30CF\u77ED\u8ABF (Cm)</option>
15527
+ <option value="key_Csm" title="\u843D\u80C6\u3001\u6CE3\u304D\u53EB\u3093\u3060\u3001\u60B2\u6D99\u306E\u3001\u6DF1\u3044\u5606\u304D">\u5B30\u30CF\u77ED\u8ABF (C\u266Fm)</option>
15528
+ <option value="key_Dm" title="\u53B3\u7C9B\u306A\u3001\u656C\u8654\u306A\u3001\u601D\u7D22\u7684\u306A\u3001\u91CD\u539A\u306A\u7948\u308A">\u30CB\u77ED\u8ABF (Dm)</option>
15529
+ <option value="key_Ebm" title="\u6DF1\u3044\u82E6\u60A9\u3001\u5B9F\u5B58\u7684\u306A\u4E0D\u5B89\u3001\u6226\u6144">\u5909\u30DB\u77ED\u8ABF (E\u266Dm)</option>
15530
+ <option value="key_Em" title="\u5F31\u3005\u3057\u3044\u3001\u306A\u307E\u3081\u304B\u3057\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044">\u30DB\u77ED\u8ABF (Em)</option>
15531
+ <option value="key_Fm" title="\u307C\u3093\u3084\u308A\u3057\u305F\u3001\u7269\u60B2\u3057\u3044\u3001\u3057\u3081\u3084\u304B\u306A\u3001\u846C\u9001">\u30D8\u77ED\u8ABF (Fm)</option>
15532
+ <option value="key_Fsm" title="\u9670\u6C17\u306A\u3001\u6FC0\u3057\u3044\u61A4\u308A\u3001\u6697\u3044\u60C5\u5FF5">\u5B30\u30D8\u77ED\u8ABF (F\u266Fm)</option>
15533
+ <option value="key_Gm" title="\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u3084\u308B\u305B\u306A\u3055\u3001\u60B2\u75DB\u306A\u53EB\u3073">\u30C8\u77ED\u8ABF (Gm)</option>
15534
+ <option value="key_Abm" title="\u4E0D\u670D\u306A\u3001\u5606\u304D\u306E\u3001\u6CE3\u304D\u53EB\u3093\u3060">\u5909\u30A4\u77ED\u8ABF (A\u266Dm)</option>
15535
+ </optgroup>
15536
+ </select>
15537
+ <span class="dtm-grow"></span>
15538
+ <span class="dtm-hint" data-dtm="compose-key-hint"></span>
15539
+ </div>
15385
15540
  <div class="dtm-row">
15386
15541
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
15387
15542
  <select class="dtm-select" data-dtm="shift-select">
@@ -15577,6 +15732,8 @@ var buildUI = (target, options) => {
15577
15732
  macroCompose: sel("macro-compose"),
15578
15733
  composeSections: sel("compose-sections"),
15579
15734
  composeSectionsLen: sel("compose-sections-len"),
15735
+ composeKey: sel("compose-key"),
15736
+ composeKeyHint: sel("compose-key-hint"),
15580
15737
  macroComposeVocal: sel("macro-compose-vocal"),
15581
15738
  macroComposeInfo: sel("macro-compose-info"),
15582
15739
  macroClear: sel("macro-clear"),
@@ -21472,6 +21629,16 @@ var mountDAW = (target, options = {}) => {
21472
21629
  };
21473
21630
  refs.composeSections.addEventListener("change", updateComposeSectionsLen);
21474
21631
  updateComposeSectionsLen();
21632
+ const updateComposeKeyHint = () => {
21633
+ if (!refs.composeKey || !refs.composeKeyHint) return;
21634
+ const desc = getComposeKeyDescription(refs.composeKey.value);
21635
+ refs.composeKeyHint.textContent = desc;
21636
+ refs.composeKeyHint.title = desc;
21637
+ };
21638
+ if (refs.composeKey) {
21639
+ refs.composeKey.addEventListener("change", updateComposeKeyHint);
21640
+ updateComposeKeyHint();
21641
+ }
21475
21642
  const runCompose = (withVocal) => {
21476
21643
  stop();
21477
21644
  overlayDuring(() => {
@@ -21479,6 +21646,7 @@ var mountDAW = (target, options = {}) => {
21479
21646
  stepsPerBar: renderConfig.stepsPerBar,
21480
21647
  edo: renderConfig.edo,
21481
21648
  sections: selectedComposeSections(),
21649
+ baseKey: refs.composeKey?.value ?? "any",
21482
21650
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
21483
21651
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
21484
21652
  recent: recentComposeFingerprints
@@ -21551,7 +21719,10 @@ var mountDAW = (target, options = {}) => {
21551
21719
  }
21552
21720
  }
21553
21721
  setBpm(song.bpm);
21554
- applyComposedDrumPattern(song.drums.def);
21722
+ currentDrumPattern = song.drum;
21723
+ refs.drumSelect.value = song.drum;
21724
+ options.onDrumChange?.(song.drum);
21725
+ applyDrumPatternFont(song.drum);
21555
21726
  for (const t of bassTracksForCompose()) {
21556
21727
  t.trackCompression = AUTO_ROLE_MIX.bass.compression;
21557
21728
  options.onTrackCompressionChange?.(t.config.id, t.trackCompression);
@@ -21570,6 +21741,10 @@ var mountDAW = (target, options = {}) => {
21570
21741
  fireLyricsChange(melodyTrack);
21571
21742
  }
21572
21743
  }
21744
+ if (refs.composeKeyHint) {
21745
+ refs.composeKeyHint.textContent = `${song.keyLabel} \u3067\u4F5C\u6210`;
21746
+ refs.composeKeyHint.title = `${song.keyLabel}${song.moodLabel ? `\uFF08${song.moodLabel}\uFF09` : ""}`;
21747
+ }
21573
21748
  playStartStep = 0;
21574
21749
  redrawAll();
21575
21750
  updateTrackPanel();
@@ -21920,9 +22095,6 @@ var mountDAW = (target, options = {}) => {
21920
22095
  extractedMidiDrum = patternDef.pattern;
21921
22096
  installDrumPattern("_extracted_midi", patternDef);
21922
22097
  };
21923
- const applyComposedDrumPattern = (patternDef) => {
21924
- installDrumPattern("_composed", patternDef);
21925
- };
21926
22098
  const wireMidi = () => {
21927
22099
  refs.midiInput.addEventListener("change", async () => {
21928
22100
  const file = refs.midiInput.files?.[0];
@@ -24441,7 +24613,7 @@ var createDtmStudio = async (options = {}) => {
24441
24613
  await loadInstrument(key);
24442
24614
  }
24443
24615
  };
24444
- void Promise.all([
24616
+ const presetPromise = Promise.all([
24445
24617
  loadPreset(
24446
24618
  playerPreset,
24447
24619
  loadTrackIds,
@@ -24481,25 +24653,29 @@ var createDtmStudio = async (options = {}) => {
24481
24653
  duration: e.duration
24482
24654
  });
24483
24655
  };
24656
+ const { onResumeAudio: userOnResumeAudio, ...playerOpts } = opts;
24484
24657
  const player = mountMmlPlayer(target, mml, {
24658
+ ...playerOpts,
24485
24659
  getAudioTime: () => audioCtx.currentTime,
24486
24660
  onResumeAudio: async () => {
24487
24661
  await resumeAudio();
24662
+ await presetPromise;
24488
24663
  if (meta.drum) {
24489
24664
  await loadRequiredDrums(
24490
24665
  getDrumPatternKeys(
24491
24666
  meta.drum,
24492
- resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
24667
+ resolveDrumPatterns(
24668
+ playerOpts.drumPatterns ?? options.drumPatterns
24669
+ )
24493
24670
  ),
24494
24671
  meta.drumFont || "FluidR3_GM_sf2_file:0"
24495
24672
  );
24496
24673
  }
24497
- await opts.onResumeAudio?.();
24674
+ await userOnResumeAudio?.();
24498
24675
  },
24499
24676
  onPlayNote: playPlayerNote,
24500
24677
  onPlayDrum: playDrum,
24501
- singingVoices,
24502
- ...opts
24678
+ singingVoices
24503
24679
  });
24504
24680
  mountedPlayers.push(player);
24505
24681
  const destroy = () => {
@@ -24532,7 +24708,7 @@ var createDtmStudio = async (options = {}) => {
24532
24708
  await loadInstrument(key);
24533
24709
  }
24534
24710
  };
24535
- void Promise.all([
24711
+ const presetPromise = Promise.all([
24536
24712
  loadPreset(
24537
24713
  playerPreset,
24538
24714
  loadTrackIds,
@@ -24572,8 +24748,9 @@ var createDtmStudio = async (options = {}) => {
24572
24748
  duration: e.duration
24573
24749
  });
24574
24750
  };
24751
+ const { onResumeAudio: userOnResumeAudio, ...playOpts } = opts;
24575
24752
  return playMML(mml, {
24576
- ...opts,
24753
+ ...playOpts,
24577
24754
  audioContext: audioCtx,
24578
24755
  destination: masterGain,
24579
24756
  synth: false,
@@ -24581,15 +24758,18 @@ var createDtmStudio = async (options = {}) => {
24581
24758
  onPlayDrum: playDrum,
24582
24759
  onResumeAudio: async () => {
24583
24760
  await resumeAudio();
24761
+ await presetPromise;
24584
24762
  if (meta.drum) {
24585
24763
  await loadRequiredDrums(
24586
24764
  getDrumPatternKeys(
24587
24765
  meta.drum,
24588
- resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
24766
+ resolveDrumPatterns(
24767
+ playOpts.drumPatterns ?? options.drumPatterns
24768
+ )
24589
24769
  )
24590
24770
  );
24591
24771
  }
24592
- await opts.onResumeAudio?.();
24772
+ await userOnResumeAudio?.();
24593
24773
  }
24594
24774
  });
24595
24775
  };
@@ -24616,7 +24796,7 @@ var createDtmStudio = async (options = {}) => {
24616
24796
  await loadInstrument(key);
24617
24797
  }
24618
24798
  };
24619
- void Promise.all([
24799
+ const presetPromise = Promise.all([
24620
24800
  loadPreset(
24621
24801
  playerPreset,
24622
24802
  loadTrackIds,
@@ -24656,8 +24836,9 @@ var createDtmStudio = async (options = {}) => {
24656
24836
  duration: e.duration
24657
24837
  });
24658
24838
  };
24839
+ const { onResumeAudio: userOnResumeAudio, ...playOpts } = opts;
24659
24840
  return playSingingMML(mml, {
24660
- ...opts,
24841
+ ...playOpts,
24661
24842
  audioContext: audioCtx,
24662
24843
  destination: masterGain,
24663
24844
  synth: false,
@@ -24666,15 +24847,18 @@ var createDtmStudio = async (options = {}) => {
24666
24847
  onPlayDrum: playDrum,
24667
24848
  onResumeAudio: async () => {
24668
24849
  await resumeAudio();
24850
+ await presetPromise;
24669
24851
  if (meta.drum) {
24670
24852
  await loadRequiredDrums(
24671
24853
  getDrumPatternKeys(
24672
24854
  meta.drum,
24673
- resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
24855
+ resolveDrumPatterns(
24856
+ playOpts.drumPatterns ?? options.drumPatterns
24857
+ )
24674
24858
  )
24675
24859
  );
24676
24860
  }
24677
- await opts.onResumeAudio?.();
24861
+ await userOnResumeAudio?.();
24678
24862
  }
24679
24863
  });
24680
24864
  };
@@ -24743,7 +24927,7 @@ var createDtmStudio = async (options = {}) => {
24743
24927
  velocity: p.velocity
24744
24928
  }));
24745
24929
  const playerPreset = defaultPreset;
24746
- void loadPreset(playerPreset, ["chord"]);
24930
+ const presetPromise = loadPreset(playerPreset, ["chord"]);
24747
24931
  const playPlayerNote = (e) => {
24748
24932
  const sfInst = resolveSoundFont(playerPreset, "chord");
24749
24933
  if (!sfInst) return;
@@ -24759,16 +24943,21 @@ var createDtmStudio = async (options = {}) => {
24759
24943
  duration: e.duration
24760
24944
  });
24761
24945
  };
24946
+ const { onResumeAudio: userOnResumeAudio, ...chordOpts } = opts;
24762
24947
  return playPlacements(placements, {
24763
- ...opts,
24948
+ ...chordOpts,
24764
24949
  audioContext: audioCtx,
24765
24950
  destination: masterGain,
24766
24951
  synth: false,
24767
24952
  onPlayNote: playPlayerNote,
24768
24953
  onPlayDrum: playDrum,
24769
- onResumeAudio: resumeAudio,
24954
+ onResumeAudio: async () => {
24955
+ await resumeAudio();
24956
+ await presetPromise;
24957
+ await userOnResumeAudio?.();
24958
+ },
24770
24959
  bpm,
24771
- metaVolume: opts.volume ?? 100
24960
+ metaVolume: chordOpts.volume ?? 100
24772
24961
  });
24773
24962
  };
24774
24963
  const mountChordPlayerInstance = (target, chords, options2 = {}) => {
@@ -24894,6 +25083,8 @@ export {
24894
25083
  A4_UNITS,
24895
25084
  BREATH_MARK,
24896
25085
  CENTS_PER_UNIT,
25086
+ COMPOSE_KEYS,
25087
+ COMPOSE_MOOD_GROUPS,
24897
25088
  CORPUS_BANDS,
24898
25089
  CORPUS_SIZE,
24899
25090
  DAW_CSS,
@@ -24920,8 +25111,10 @@ export {
24920
25111
  KOE_VOICEBANK_LABELS,
24921
25112
  KOE_VOICEBANK_TERMS,
24922
25113
  LinkedList,
25114
+ MAJOR_KEY_IDS,
24923
25115
  MAX_VOCAL_VOLUME,
24924
25116
  MICRO_STEP,
25117
+ MINOR_KEY_IDS,
24925
25118
  MMLCore,
24926
25119
  MML_END_MARKER,
24927
25120
  MidiSearchClient,
@@ -24990,6 +25183,7 @@ export {
24990
25183
  formatMmlMeta,
24991
25184
  freqFromPitch,
24992
25185
  generateRandomPattern,
25186
+ getComposeKeyDescription,
24993
25187
  getDrumPatternKeys,
24994
25188
  getMidiBPM,
24995
25189
  icon,
@@ -25019,6 +25213,7 @@ export {
25019
25213
  playNote,
25020
25214
  playPlacements,
25021
25215
  playSingingMML,
25216
+ resolveComposeKey,
25022
25217
  resolveDrumPattern,
25023
25218
  resolveLoopPoint,
25024
25219
  semitonesToUnits,