@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.js CHANGED
@@ -24,6 +24,8 @@ __export(index_exports, {
24
24
  A4_UNITS: () => A4_UNITS,
25
25
  BREATH_MARK: () => BREATH_MARK,
26
26
  CENTS_PER_UNIT: () => CENTS_PER_UNIT,
27
+ COMPOSE_KEYS: () => COMPOSE_KEYS,
28
+ COMPOSE_MOOD_GROUPS: () => COMPOSE_MOOD_GROUPS,
27
29
  CORPUS_BANDS: () => CORPUS_BANDS,
28
30
  CORPUS_SIZE: () => CORPUS_SIZE,
29
31
  DAW_CSS: () => DAW_CSS,
@@ -50,8 +52,10 @@ __export(index_exports, {
50
52
  KOE_VOICEBANK_LABELS: () => KOE_VOICEBANK_LABELS,
51
53
  KOE_VOICEBANK_TERMS: () => KOE_VOICEBANK_TERMS,
52
54
  LinkedList: () => LinkedList,
55
+ MAJOR_KEY_IDS: () => MAJOR_KEY_IDS,
53
56
  MAX_VOCAL_VOLUME: () => MAX_VOCAL_VOLUME,
54
57
  MICRO_STEP: () => MICRO_STEP,
58
+ MINOR_KEY_IDS: () => MINOR_KEY_IDS,
55
59
  MMLCore: () => MMLCore,
56
60
  MML_END_MARKER: () => MML_END_MARKER,
57
61
  MidiSearchClient: () => MidiSearchClient,
@@ -120,6 +124,7 @@ __export(index_exports, {
120
124
  formatMmlMeta: () => formatMmlMeta,
121
125
  freqFromPitch: () => freqFromPitch,
122
126
  generateRandomPattern: () => generateRandomPattern,
127
+ getComposeKeyDescription: () => getComposeKeyDescription,
123
128
  getDrumPatternKeys: () => getDrumPatternKeys,
124
129
  getMidiBPM: () => getMidiBPM,
125
130
  icon: () => icon,
@@ -149,6 +154,7 @@ __export(index_exports, {
149
154
  playNote: () => playNote,
150
155
  playPlacements: () => playPlacements,
151
156
  playSingingMML: () => playSingingMML,
157
+ resolveComposeKey: () => resolveComposeKey,
152
158
  resolveDrumPattern: () => resolveDrumPattern,
153
159
  resolveLoopPoint: () => resolveLoopPoint,
154
160
  semitonesToUnits: () => semitonesToUnits,
@@ -4389,7 +4395,7 @@ var NATURAL_STEPS2 = {
4389
4395
  };
4390
4396
  var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
4391
4397
  var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
4392
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w-]+)/gi;
4398
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo)=([\w:-]+)/gi;
4393
4399
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
4394
4400
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
4395
4401
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
@@ -8910,6 +8916,27 @@ var DAW_CSS = `
8910
8916
  .dtm-player-play:active { transform: translate(2px, 2px); box-shadow: none; }
8911
8917
  .dtm-player-play--stop { background: var(--dtm-danger); }
8912
8918
  .dtm-player-play:disabled { opacity: 0.4; cursor: default; }
8919
+ .dtm-player-loop {
8920
+ display: inline-flex;
8921
+ align-items: center;
8922
+ justify-content: center;
8923
+ width: 28px;
8924
+ height: 28px;
8925
+ padding: 0;
8926
+ border: 2px solid var(--dtm-border2);
8927
+ background: var(--dtm-bg);
8928
+ color: var(--dtm-muted);
8929
+ cursor: pointer;
8930
+ flex: 0 0 auto;
8931
+ }
8932
+ .dtm-player-loop:active { transform: translate(1px, 1px); }
8933
+ .dtm-player-loop--on {
8934
+ border-color: var(--c-black);
8935
+ background: var(--dtm-gold);
8936
+ color: var(--c-black);
8937
+ box-shadow: 2px 2px 0 var(--c-black);
8938
+ }
8939
+ .dtm-player-loop:disabled { opacity: 0.4; cursor: default; }
8913
8940
  .dtm-player-beat-row {
8914
8941
  display: flex;
8915
8942
  align-items: center;
@@ -9568,6 +9595,12 @@ var FALLBACK_VOCAL_ICON = Chip_default;
9568
9595
  var STEPS_PER_BEAT2 = 48;
9569
9596
  var STEPS_PER_BAR2 = 192;
9570
9597
  var DEFAULT_TRACK_COLORS = ["#00e436", "#29adff", "#ff77a8", "#ffec27"];
9598
+ var parseLoopMeta = (mml) => {
9599
+ const m = mml.match(/^#\s*loop\s*=\s*(on|off|1|0|true|false)\s*$/im);
9600
+ if (!m) return null;
9601
+ const v = m[1].toLowerCase();
9602
+ return v === "on" || v === "1" || v === "true";
9603
+ };
9571
9604
  var activePlayer = null;
9572
9605
  var agreedModelsInSession = /* @__PURE__ */ new Set();
9573
9606
  var LYRIC_MODEL_LABELS = {
@@ -9807,6 +9840,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9807
9840
  const colors = options.trackColors ?? DEFAULT_TRACK_COLORS;
9808
9841
  const useSynth = options.synth ?? !options.onPlayNote;
9809
9842
  const secondsPerStep = 60 / bpm / STEPS_PER_BEAT2;
9843
+ let loopEnabled = options.loop ?? parseLoopMeta(mml) ?? false;
9810
9844
  const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
9811
9845
  (a, b) => a - b
9812
9846
  );
@@ -9895,6 +9929,13 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9895
9929
  playBtn.className = "dtm-player-play";
9896
9930
  playBtn.innerHTML = icon("play", 12);
9897
9931
  playBtn.disabled = trackIndices.length === 0;
9932
+ const loopBtn = doc.createElement("button");
9933
+ loopBtn.type = "button";
9934
+ loopBtn.className = "dtm-player-loop";
9935
+ loopBtn.innerHTML = icon("loop", 12);
9936
+ loopBtn.title = loopEnabled ? "LOOP ON" : "LOOP OFF";
9937
+ loopBtn.classList.toggle("dtm-player-loop--on", loopEnabled);
9938
+ loopBtn.disabled = trackIndices.length === 0;
9898
9939
  const mutedTracks = /* @__PURE__ */ new Set();
9899
9940
  const labelByTrack = /* @__PURE__ */ new Map();
9900
9941
  const emojiByTrack = /* @__PURE__ */ new Map();
@@ -10327,7 +10368,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
10327
10368
  chordEl.className = "dtm-player-chord";
10328
10369
  chordEl.textContent = "";
10329
10370
  beatRow.appendChild(chordEl);
10330
- head.append(playBtn, beatRow, dots, mmlHeader);
10371
+ head.append(playBtn, loopBtn, beatRow, dots, mmlHeader);
10331
10372
  root.appendChild(head);
10332
10373
  const seekRow = doc.createElement("div");
10333
10374
  seekRow.className = "dtm-player-seek-row";
@@ -10627,6 +10668,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
10627
10668
  getPlayStartStep: () => 0,
10628
10669
  getDrumPattern: (currentBar) => resolveDrumPattern(drumPatternName, drumPatternDict, currentBar),
10629
10670
  getSoloTrackId: () => null,
10671
+ getLoop: () => loopEnabled,
10630
10672
  getAudioTime,
10631
10673
  onPlayNote: (e) => {
10632
10674
  const trackIdx = Number(e.trackId);
@@ -10743,8 +10785,16 @@ var mountMmlPlayer = (target, mml, options = {}) => {
10743
10785
  if (streaming && !skipSinging) {
10744
10786
  const v = ensureVoices();
10745
10787
  v.setVolume(trackVolume / 100 * (masterVolume / 100));
10788
+ let loopLengthSec;
10789
+ let loopStartSec;
10790
+ if (loopEnabled) {
10791
+ loopStartSec = 0;
10792
+ loopLengthSec = maxStep * secondsPerStep;
10793
+ }
10746
10794
  v.startStream(tracks, seq.getStartTime(), {
10747
10795
  isAudible: (t) => !mutedTracks.has(Number(t.id)),
10796
+ loopLengthSec,
10797
+ loopStartSec,
10748
10798
  onLateSkip: () => {
10749
10799
  showPlayerMessage(
10750
10800
  "\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"
@@ -10816,6 +10866,14 @@ var mountMmlPlayer = (target, mml, options = {}) => {
10816
10866
  if (playing) pause();
10817
10867
  else play(Number(seekInput.value));
10818
10868
  });
10869
+ loopBtn.addEventListener("click", () => {
10870
+ loopEnabled = !loopEnabled;
10871
+ loopBtn.classList.toggle("dtm-player-loop--on", loopEnabled);
10872
+ loopBtn.title = loopEnabled ? "LOOP ON" : "LOOP OFF";
10873
+ if (playing) {
10874
+ seekTo(playStep);
10875
+ }
10876
+ });
10819
10877
  const destroy = () => {
10820
10878
  doc.removeEventListener("click", handleOutsideClick);
10821
10879
  seq.stop();
@@ -11355,7 +11413,7 @@ var parseBpmMeta = (chords) => {
11355
11413
  if (Number.isNaN(val)) return null;
11356
11414
  return Math.max(40, Math.min(240, val));
11357
11415
  };
11358
- var parseLoopMeta = (chords) => {
11416
+ var parseLoopMeta2 = (chords) => {
11359
11417
  const m = chords.match(/^#\s*loop\s*=\s*(on|off|1|0|true|false)\s*$/im);
11360
11418
  if (!m) return null;
11361
11419
  const v = m[1].toLowerCase();
@@ -11532,7 +11590,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
11532
11590
  let masterVolume = options.volume ?? 80;
11533
11591
  let isLoading = false;
11534
11592
  let loadAbortId = 0;
11535
- let loopEnabled = options.loop ?? parseLoopMeta(chords) ?? false;
11593
+ let loopEnabled = options.loop ?? parseLoopMeta2(chords) ?? false;
11536
11594
  let metronomeEnabled = parseMetronomeMeta(chords);
11537
11595
  let playOffsetSec = 0;
11538
11596
  let currentPlaySec = 0;
@@ -12598,292 +12656,356 @@ var CORPUS_MEDIANS = {
12598
12656
  complementarity: 0.045
12599
12657
  };
12600
12658
 
12601
- // src/compose-drums.ts
12602
- var DRUM_STYLE_LABELS = {
12603
- eight: "8\u30D3\u30FC\u30C8",
12604
- sixteen: "16\u30D3\u30FC\u30C8",
12605
- four: "4\u3064\u6253\u3061",
12606
- shuffle: "\u30B7\u30E3\u30C3\u30D5\u30EB",
12607
- ballad: "\u30D0\u30E9\u30FC\u30C9",
12608
- rock: "\u30ED\u30C3\u30AF"
12609
- };
12610
- var KICKS = {
12611
- // [抑えめ, 標準, 最大]
12612
- eight: [
12613
- [0, 8],
12614
- [0, 6, 8],
12615
- [0, 6, 8, 14]
12616
- ],
12617
- sixteen: [
12618
- [0, 3, 8],
12619
- [0, 3, 8, 11],
12620
- [0, 3, 6, 8, 11, 14]
12621
- ],
12622
- four: [
12623
- [0, 4, 8, 12],
12624
- [0, 4, 8, 12],
12625
- [0, 4, 8, 12, 14]
12626
- ],
12627
- shuffle: [
12628
- [0, 8],
12629
- [0, 8, 11],
12630
- [0, 5, 8, 11]
12631
- ],
12632
- ballad: [[0], [0, 8], [0, 8, 12]],
12633
- rock: [
12634
- [0, 8],
12635
- [0, 3, 8],
12636
- [0, 3, 8, 10, 14]
12637
- ]
12638
- };
12639
- var SNARES = {
12640
- eight: [
12641
- [4, 12],
12642
- [4, 12],
12643
- [4, 12]
12644
- ],
12645
- sixteen: [
12646
- [4, 12],
12647
- [4, 12],
12648
- [4, 12]
12649
- ],
12650
- four: [
12651
- [4, 12],
12652
- [4, 12],
12653
- [4, 12]
12654
- ],
12655
- shuffle: [
12656
- [4, 12],
12657
- [4, 12],
12658
- [4, 12]
12659
- ],
12660
- // バラードはサイドスティック(リムショット)から入って、盛り上がりでスネアへ移る。
12661
- ballad: [[12], [4, 12], [4, 12]],
12662
- rock: [
12663
- [4, 12],
12664
- [4, 12],
12665
- [4, 12]
12666
- ]
12667
- };
12668
- var GHOSTS = {
12669
- eight: [7],
12670
- sixteen: [7, 11, 15],
12671
- four: [],
12672
- shuffle: [11],
12673
- ballad: [],
12674
- rock: [7, 15]
12675
- };
12676
- var HIHATS = {
12677
- eight: [
12678
- [0, 4, 8, 12],
12679
- [0, 2, 4, 6, 8, 10, 12, 14],
12680
- [0, 2, 4, 6, 8, 10, 12, 14]
12681
- ],
12682
- sixteen: [
12683
- [0, 2, 4, 6, 8, 10, 12, 14],
12684
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
12685
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
12686
- ],
12687
- four: [
12688
- [2, 6, 10, 14],
12689
- [0, 2, 4, 6, 8, 10, 12, 14],
12690
- [0, 2, 4, 6, 8, 10, 12, 14]
12691
- ],
12692
- shuffle: [[], [], []],
12693
- // 三連なので格子では書けない。{@link shuffleHats} が作る
12694
- ballad: [
12695
- [0, 8],
12696
- [0, 4, 8, 12],
12697
- [0, 2, 4, 6, 8, 10, 12, 14]
12698
- ],
12699
- rock: [
12700
- [0, 4, 8, 12],
12701
- [0, 2, 4, 6, 8, 10, 12, 14],
12702
- [0, 2, 4, 6, 8, 10, 12, 14]
12703
- ]
12704
- };
12705
- var FILLS = [
12706
- // スネアの16分の詰め
12659
+ // src/compose-keys.ts
12660
+ var COMPOSE_KEYS = {
12661
+ // --- 長調 (12調) ---
12662
+ key_C: {
12663
+ id: "key_C",
12664
+ name: "C",
12665
+ label: "\u30CF\u9577\u8ABF (C)",
12666
+ mode: "major",
12667
+ rootShift: 0,
12668
+ moodId: "mood_happy",
12669
+ description: "\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7D14\u7C8B\u3001\u7D20\u6734\u3001\u51FA\u767A"
12670
+ },
12671
+ key_Db: {
12672
+ id: "key_Db",
12673
+ name: "D\u266D",
12674
+ label: "\u5909\u30CB\u9577\u8ABF (D\u266D)",
12675
+ mode: "major",
12676
+ rootShift: 1,
12677
+ moodId: "mood_melancholy",
12678
+ description: "\u60B2\u3057\u307F\u3001\u6182\u9B31\u306A\u3001\u7518\u7F8E\u306A\u611F\u50B7\uFF08\u5B30\u30CF\u9577\u8ABF\u3068\u540C\u97F3\uFF09"
12679
+ },
12680
+ key_D: {
12681
+ id: "key_D",
12682
+ name: "D",
12683
+ label: "\u30CB\u9577\u8ABF (D)",
12684
+ mode: "major",
12685
+ rootShift: 2,
12686
+ moodId: "mood_triumphant",
12687
+ description: "\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0\u3001\u83EF\u3084\u304B"
12688
+ },
12689
+ key_Eb: {
12690
+ id: "key_Eb",
12691
+ name: "E\u266D",
12692
+ label: "\u5909\u30DB\u9577\u8ABF (E\u266D)",
12693
+ mode: "major",
12694
+ rootShift: 3,
12695
+ moodId: "mood_dark",
12696
+ description: "\u53B3\u3057\u3044\u3001\u304D\u3064\u3044\u3001\u305D\u308C\u3067\u3044\u3066\u611B\u306B\u6E80\u3061\u305F\u3001\u8358\u91CD"
12697
+ },
12698
+ key_E: {
12699
+ id: "key_E",
12700
+ name: "E",
12701
+ label: "\u30DB\u9577\u8ABF (E)",
12702
+ mode: "major",
12703
+ rootShift: 4,
12704
+ moodId: "mood_fierce",
12705
+ description: "\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u8352\u3005\u3057\u3044\u3001\u8F1D\u304B\u3057\u3044\u60C5\u71B1"
12706
+ },
12707
+ key_F: {
12708
+ id: "key_F",
12709
+ name: "F",
12710
+ label: "\u30D8\u9577\u8ABF (F)",
12711
+ mode: "major",
12712
+ rootShift: 5,
12713
+ moodId: "mood_fierce",
12714
+ description: "\u6012\u308A\u72C2\u3063\u305F\u3001\u6C17\u6027\u306E\u8352\u3044\u3001\u4E00\u6642\u7684\u306A\u60B2\u5606\u3001\u6FC0\u52D5"
12715
+ },
12716
+ key_Gb: {
12717
+ id: "key_Gb",
12718
+ name: "G\u266D",
12719
+ label: "\u5909\u30C8\u9577\u8ABF (G\u266D)",
12720
+ mode: "major",
12721
+ rootShift: 6,
12722
+ moodId: "mood_triumphant",
12723
+ 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"
12724
+ },
12725
+ key_G: {
12726
+ id: "key_G",
12727
+ name: "G",
12728
+ label: "\u30C8\u9577\u8ABF (G)",
12729
+ mode: "major",
12730
+ rootShift: -5,
12731
+ moodId: "mood_solemn",
12732
+ description: "\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F\u3001\u5E83\u304C\u308A"
12733
+ },
12734
+ key_Ab: {
12735
+ id: "key_Ab",
12736
+ name: "A\u266D",
12737
+ label: "\u5909\u30A4\u9577\u8ABF (A\u266D)",
12738
+ mode: "major",
12739
+ rootShift: -4,
12740
+ moodId: "mood_dark",
12741
+ description: "\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6DF1\u9060\u306A\u7791\u60F3"
12742
+ },
12743
+ key_A: {
12744
+ id: "key_A",
12745
+ name: "A",
12746
+ label: "\u30A4\u9577\u8ABF (A)",
12747
+ mode: "major",
12748
+ rootShift: -3,
12749
+ moodId: "mood_happy",
12750
+ description: "\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D\u3001\u304D\u3089\u3073\u3084\u304B"
12751
+ },
12752
+ key_Bb: {
12753
+ id: "key_Bb",
12754
+ name: "B\u266D",
12755
+ label: "\u5909\u30ED\u9577\u8ABF (B\u266D)",
12756
+ mode: "major",
12757
+ rootShift: -2,
12758
+ moodId: "mood_happy",
12759
+ description: "\u559C\u3070\u3057\u3044\u3001\u98A8\u5909\u308F\u308A\u306A\u3001\u967D\u6C17\u306A\u3001\u8EFD\u5FEB"
12760
+ },
12761
+ key_B: {
12762
+ id: "key_B",
12763
+ name: "B",
12764
+ label: "\u30ED\u9577\u8ABF (B)",
12765
+ mode: "major",
12766
+ rootShift: -1,
12767
+ moodId: "mood_fierce",
12768
+ description: "\u3069\u304E\u3064\u3044\u3001\u5F37\u70C8\u306A\u3001\u8352\u3063\u307D\u3044\u3001\u731B\u70C8"
12769
+ },
12770
+ // --- 短調 (12調) ---
12771
+ key_Am: {
12772
+ id: "key_Am",
12773
+ name: "Am",
12774
+ label: "\u30A4\u77ED\u8ABF (Am)",
12775
+ mode: "minor",
12776
+ rootShift: 0,
12777
+ moodId: "mood_solemn",
12778
+ description: "\u67D4\u3089\u304B\u306A\u3001\u7269\u60B2\u3057\u3044\u3001\u656C\u8654\u306A\u3001\u7D20\u6734\u306A\u54C0\u6101"
12779
+ },
12780
+ key_Bbm: {
12781
+ id: "key_Bbm",
12782
+ name: "B\u266Dm",
12783
+ label: "\u5909\u30ED\u77ED\u8ABF (B\u266Dm)",
12784
+ mode: "minor",
12785
+ rootShift: 1,
12786
+ moodId: "mood_dark",
12787
+ 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"
12788
+ },
12789
+ key_Bm: {
12790
+ id: "key_Bm",
12791
+ name: "Bm",
12792
+ label: "\u30ED\u77ED\u8ABF (Bm)",
12793
+ mode: "minor",
12794
+ rootShift: 2,
12795
+ moodId: "mood_melancholy",
12796
+ description: "\u5B64\u72EC\u306A\u3001\u6182\u9B31\u306A\u3001\u5FCD\u8010\u3001\u9759\u304B\u306A\u8AE6\u5FF5"
12797
+ },
12798
+ key_Cm: {
12799
+ id: "key_Cm",
12800
+ name: "Cm",
12801
+ label: "\u30CF\u77ED\u8ABF (Cm)",
12802
+ mode: "minor",
12803
+ rootShift: 3,
12804
+ moodId: "mood_plaintive",
12805
+ description: "\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u306A\u3001\u604B\u308F\u305A\u3089\u3044\u306E\u3001\u60B2\u5287\u7684"
12806
+ },
12807
+ key_Csm: {
12808
+ id: "key_Csm",
12809
+ name: "C\u266Fm",
12810
+ label: "\u5B30\u30CF\u77ED\u8ABF (C\u266Fm)",
12811
+ mode: "minor",
12812
+ rootShift: 4,
12813
+ moodId: "mood_melancholy",
12814
+ description: "\u843D\u80C6\u3001\u6CE3\u304D\u53EB\u3093\u3060\u3001\u60B2\u6D99\u306E\u3001\u6DF1\u3044\u5606\u304D"
12815
+ },
12816
+ key_Dm: {
12817
+ id: "key_Dm",
12818
+ name: "Dm",
12819
+ label: "\u30CB\u77ED\u8ABF (Dm)",
12820
+ mode: "minor",
12821
+ rootShift: 5,
12822
+ moodId: "mood_solemn",
12823
+ description: "\u53B3\u7C9B\u306A\u3001\u656C\u8654\u306A\u3001\u601D\u7D22\u7684\u306A\u3001\u91CD\u539A\u306A\u7948\u308A"
12824
+ },
12825
+ key_Ebm: {
12826
+ id: "key_Ebm",
12827
+ name: "E\u266Dm",
12828
+ label: "\u5909\u30DB\u77ED\u8ABF (E\u266Dm)",
12829
+ mode: "minor",
12830
+ rootShift: 6,
12831
+ moodId: "mood_anxious",
12832
+ description: "\u6DF1\u3044\u82E6\u60A9\u3001\u5B9F\u5B58\u7684\u306A\u4E0D\u5B89\u3001\u6226\u6144\uFF08\u5B30\u30CB\u77ED\u8ABF\u3068\u540C\u97F3\uFF09"
12833
+ },
12834
+ key_Em: {
12835
+ id: "key_Em",
12836
+ name: "Em",
12837
+ label: "\u30DB\u77ED\u8ABF (Em)",
12838
+ mode: "minor",
12839
+ rootShift: -5,
12840
+ moodId: "mood_plaintive",
12841
+ description: "\u5F31\u3005\u3057\u3044\u3001\u306A\u307E\u3081\u304B\u3057\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044\u3001\u7E4A\u7D30"
12842
+ },
12843
+ key_Fm: {
12844
+ id: "key_Fm",
12845
+ name: "Fm",
12846
+ label: "\u30D8\u77ED\u8ABF (Fm)",
12847
+ mode: "minor",
12848
+ rootShift: -4,
12849
+ moodId: "mood_plaintive",
12850
+ description: "\u307C\u3093\u3084\u308A\u3057\u305F\u3001\u7269\u60B2\u3057\u3044\u3001\u3057\u3081\u3084\u304B\u306A\u3001\u846C\u9001"
12851
+ },
12852
+ key_Fsm: {
12853
+ id: "key_Fsm",
12854
+ name: "F\u266Fm",
12855
+ label: "\u5B30\u30D8\u77ED\u8ABF (F\u266Fm)",
12856
+ mode: "minor",
12857
+ rootShift: -3,
12858
+ moodId: "mood_anxious",
12859
+ description: "\u9670\u6C17\u306A\u3001\u6FC0\u3057\u3044\u61A4\u308A\u3001\u6697\u3044\u60C5\u5FF5"
12860
+ },
12861
+ key_Gm: {
12862
+ id: "key_Gm",
12863
+ name: "Gm",
12864
+ label: "\u30C8\u77ED\u8ABF (Gm)",
12865
+ mode: "minor",
12866
+ rootShift: -2,
12867
+ moodId: "mood_anxious",
12868
+ description: "\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u3084\u308B\u305B\u306A\u3055\u3001\u60B2\u75DB\u306A\u53EB\u3073"
12869
+ },
12870
+ key_Abm: {
12871
+ id: "key_Abm",
12872
+ name: "A\u266Dm",
12873
+ label: "\u5909\u30A4\u77ED\u8ABF (A\u266Dm)",
12874
+ mode: "minor",
12875
+ rootShift: -1,
12876
+ moodId: "mood_anxious",
12877
+ description: "\u4E0D\u670D\u306A\u3001\u5606\u304D\u306E\u3001\u6CE3\u304D\u53EB\u3093\u3060\uFF08\u5B30\u30C8\u77ED\u8ABF\u3068\u540C\u97F3\uFF09"
12878
+ }
12879
+ };
12880
+ var COMPOSE_MOOD_GROUPS = [
12707
12881
  {
12708
- grid: [12, 13, 14, 15],
12709
- drums: [
12710
- DRUM_KEYS.acousticSnare,
12711
- DRUM_KEYS.acousticSnare,
12712
- DRUM_KEYS.acousticSnare,
12713
- DRUM_KEYS.acousticSnare
12714
- ]
12882
+ id: "mood_happy",
12883
+ label: "\u559C\u3070\u3057\u3044\u30FB\u967D\u6C17\u306A\u66F2",
12884
+ 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",
12885
+ keyIds: ["key_C", "key_A", "key_Bb"]
12715
12886
  },
12716
- // タムで降りる
12717
12887
  {
12718
- grid: [8, 10, 12, 14],
12719
- drums: [
12720
- DRUM_KEYS.highTom,
12721
- DRUM_KEYS.highTom,
12722
- DRUM_KEYS.lowMidTom,
12723
- DRUM_KEYS.lowTom
12724
- ]
12888
+ id: "mood_triumphant",
12889
+ label: "\u52DD\u5229\u30FB\u529B\u5F37\u3044\u66F2",
12890
+ description: "\u610F\u6C17\u63DA\u3005\u3001\u52DD\u5229\u306E\u558A\u58F0\u3001\u56F0\u96E3\u306E\u6253\u7834\u3001\u5B89\u5835\u306E\u305F\u3081\u606F",
12891
+ keyIds: ["key_D", "key_Gb"]
12725
12892
  },
12726
- // スネア → タム
12727
12893
  {
12728
- grid: [8, 9, 10, 11, 12, 14],
12729
- drums: [
12730
- DRUM_KEYS.acousticSnare,
12731
- DRUM_KEYS.acousticSnare,
12732
- DRUM_KEYS.highTom,
12733
- DRUM_KEYS.highTom,
12734
- DRUM_KEYS.lowMidTom,
12735
- DRUM_KEYS.lowTom
12736
- ]
12894
+ id: "mood_fierce",
12895
+ label: "\u6FC0\u3057\u3044\u30FB\u8352\u3005\u3057\u3044\u66F2",
12896
+ 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",
12897
+ keyIds: ["key_E", "key_F", "key_B"]
12737
12898
  },
12738
- // 短く締める
12739
12899
  {
12740
- grid: [14, 15],
12741
- drums: [DRUM_KEYS.acousticSnare, DRUM_KEYS.acousticSnare]
12900
+ id: "mood_solemn",
12901
+ label: "\u53B3\u7C9B\u30FB\u5E7B\u60F3\u7684\u306A\u66F2",
12902
+ 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",
12903
+ keyIds: ["key_G", "key_Dm", "key_Am"]
12742
12904
  },
12743
- // 16分の連打から解放
12744
12905
  {
12745
- grid: [8, 9, 10, 11, 12, 13, 14, 15],
12746
- drums: [
12747
- DRUM_KEYS.acousticSnare,
12748
- DRUM_KEYS.acousticSnare,
12749
- DRUM_KEYS.acousticSnare,
12750
- DRUM_KEYS.acousticSnare,
12751
- DRUM_KEYS.highTom,
12752
- DRUM_KEYS.highTom,
12753
- DRUM_KEYS.lowMidTom,
12754
- DRUM_KEYS.lowTom
12755
- ]
12906
+ id: "mood_plaintive",
12907
+ label: "\u7269\u60B2\u3057\u3044\u30FB\u54C0\u6101\u306E\u66F2",
12908
+ 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",
12909
+ keyIds: ["key_Cm", "key_Em", "key_Fm"]
12910
+ },
12911
+ {
12912
+ id: "mood_melancholy",
12913
+ label: "\u6182\u9B31\u30FB\u5B64\u72EC\u306A\u66F2",
12914
+ description: "\u60B2\u3057\u307F\u3001\u6182\u9B31\u3001\u5B64\u72EC\u3001\u5FCD\u8010\u3001\u843D\u80C6\u3001\u60B2\u6D99\u306E\u5606\u304D",
12915
+ keyIds: ["key_Db", "key_Bm", "key_Csm"]
12916
+ },
12917
+ {
12918
+ id: "mood_anxious",
12919
+ label: "\u4E0D\u5B89\u30FB\u82E6\u60A9\u306A\u66F2",
12920
+ 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",
12921
+ keyIds: ["key_Gm", "key_Ebm", "key_Fsm", "key_Abm"]
12756
12922
  },
12757
- // 3連のタム回し(16分格子の外)
12758
12923
  {
12759
- grid: [12, 13.33, 14.67],
12760
- drums: [DRUM_KEYS.highTom, DRUM_KEYS.lowMidTom, DRUM_KEYS.lowTom]
12924
+ id: "mood_dark",
12925
+ label: "\u6697\u95C7\u30FB\u91CD\u539A\u306A\u66F2",
12926
+ description: "\u53B3\u3057\u3044\u611B\u3001\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6050\u308D\u3057\u3044\u6697\u95C7\u3001\u5632\u308A",
12927
+ keyIds: ["key_Eb", "key_Ab", "key_Bbm"]
12761
12928
  }
12762
12929
  ];
12763
- var pick = (items, rnd) => items[Math.floor(rnd() * items.length)];
12764
- var shuffleHats = (stepsPerBar, level) => {
12765
- const beat = stepsPerBar / 4;
12766
- const triplet = beat / 3;
12767
- const out = [];
12768
- for (let b = 0; b < 4; b++) {
12769
- out.push(b * beat);
12770
- out.push(b * beat + triplet * 2);
12771
- if (level === 2) out.push(b * beat + triplet);
12930
+ var MAJOR_KEY_IDS = Object.values(COMPOSE_KEYS).filter((k) => k.mode === "major").map((k) => k.id);
12931
+ var MINOR_KEY_IDS = Object.values(COMPOSE_KEYS).filter((k) => k.mode === "minor").map((k) => k.id);
12932
+ var pickItem = (list, rnd) => list[Math.floor(rnd() * list.length)];
12933
+ var resolveComposeKey = (choice, rnd = Math.random) => {
12934
+ const c = (choice ?? "").trim() || "any";
12935
+ if (COMPOSE_KEYS[c]) {
12936
+ const target2 = COMPOSE_KEYS[c];
12937
+ const mood2 = COMPOSE_MOOD_GROUPS.find((m) => m.id === target2.moodId);
12938
+ return {
12939
+ mode: target2.mode,
12940
+ rootShift: target2.rootShift,
12941
+ keyName: target2.name,
12942
+ keyLabel: target2.label,
12943
+ moodLabel: mood2?.label,
12944
+ description: target2.description
12945
+ };
12772
12946
  }
12773
- return out;
12774
- };
12775
- var composeDrumPattern = (options) => {
12776
- const { bars, stepsPerBar, rnd } = options;
12777
- const style = options.style ?? pick(
12778
- ["eight", "sixteen", "four", "shuffle", "ballad", "rock"],
12779
- rnd
12780
- );
12781
- const toStep = (g) => Math.round(g * stepsPerBar / 16) % stepsPerBar;
12782
- const fillA = pick(FILLS, rnd);
12783
- const fillB = pick(
12784
- FILLS.filter((f) => f !== fillA),
12785
- rnd
12786
- );
12787
- const openTail = rnd() < 0.6;
12788
- const useClap = style === "four" && rnd() < 0.5;
12789
- const levels = options.levels;
12790
- const crashSet = new Set(options.crashBars ?? [1, 9, 13]);
12791
- const fillSet = new Set(
12792
- options.fillBars ?? [4, 8, 12, bars - 1].filter((b) => b > 0)
12793
- );
12794
- const sectionLevel = (bar) => {
12795
- if (levels) return levels[bar - 1] ?? 1;
12796
- if (bar <= 4) return 0;
12797
- if (bar <= 8) return 1;
12798
- if (bar <= 12) return 2;
12799
- return 1;
12800
- };
12801
- const barPatterns = [];
12802
- for (let bar = 1; bar <= bars; bar++) {
12803
- const level = sectionLevel(bar);
12804
- const isLast = bar === bars;
12805
- const isFill = !isLast && fillSet.has(bar);
12806
- const hits = [];
12807
- if (crashSet.has(bar) || isLast)
12808
- hits.push({
12809
- step: 0,
12810
- pitch: DRUM_KEYS.crashCymbal1,
12811
- velocity: bar === 9 || isLast ? 1 : 0.85
12812
- });
12813
- if (isLast) {
12814
- hits.push({ step: 0, pitch: DRUM_KEYS.bassDrum1, velocity: 1 });
12815
- hits.sort((a, b) => a.step - b.step || a.pitch - b.pitch);
12816
- barPatterns.push(hits);
12817
- continue;
12818
- }
12819
- for (const g of KICKS[style][level])
12820
- hits.push({
12821
- step: toStep(g),
12822
- pitch: DRUM_KEYS.bassDrum1,
12823
- velocity: g === 0 ? 1 : 0.88
12824
- });
12825
- const snarePitch = style === "ballad" && level === 0 ? DRUM_KEYS.sideStick : DRUM_KEYS.acousticSnare;
12826
- for (const g of SNARES[style][level]) {
12827
- hits.push({ step: toStep(g), pitch: snarePitch, velocity: 0.95 });
12828
- if (useClap)
12829
- hits.push({
12830
- step: toStep(g),
12831
- pitch: DRUM_KEYS.handClap,
12832
- velocity: 0.7
12833
- });
12834
- }
12835
- if (level >= 1)
12836
- for (const g of GHOSTS[style])
12837
- hits.push({
12838
- step: toStep(g),
12839
- pitch: DRUM_KEYS.acousticSnare,
12840
- velocity: 0.3
12841
- });
12842
- const hatSteps = style === "shuffle" ? shuffleHats(stepsPerBar, level) : HIHATS[style][level].map(toStep);
12843
- const hatPitch = level === 2 && style !== "four" ? DRUM_KEYS.rideCymbal1 : DRUM_KEYS.closedHihat;
12844
- for (const step of hatSteps) {
12845
- if (isFill && step >= stepsPerBar / 2) continue;
12846
- const isDownbeat = step % (stepsPerBar / 4) === 0;
12847
- const isTail = openTail && step === toStep(14) && !isFill;
12848
- hits.push({
12849
- step,
12850
- pitch: isTail ? DRUM_KEYS.openHihat : hatPitch,
12851
- velocity: isTail ? 0.8 : isDownbeat ? 0.75 : 0.5
12852
- });
12853
- }
12854
- if (isFill) {
12855
- const fill = bar % 8 === 0 ? fillB : fillA;
12856
- fill.grid.forEach((g, i2) => {
12857
- hits.push({
12858
- step: toStep(g),
12859
- pitch: fill.drums[i2] ?? DRUM_KEYS.acousticSnare,
12860
- // 追い込みで少しずつ強くする
12861
- velocity: 0.7 + 0.28 * i2 / Math.max(1, fill.grid.length - 1)
12862
- });
12863
- });
12864
- }
12865
- hits.sort((a, b) => a.step - b.step || a.pitch - b.pitch);
12866
- barPatterns.push(hits);
12867
- }
12868
- const pattern = [];
12869
- let runStart = 1;
12870
- for (let bar = 1; bar <= bars; bar++) {
12871
- const same = bar < bars && JSON.stringify(barPatterns[bar]) === JSON.stringify(barPatterns[bar - 1]);
12872
- if (same) continue;
12873
- const instruction = {
12874
- ranges: [[runStart, bar]],
12875
- patternBars: 1,
12876
- pattern: barPatterns[runStart - 1]
12947
+ const mood = COMPOSE_MOOD_GROUPS.find((m) => m.id === c);
12948
+ if (mood) {
12949
+ const pickedKeyId2 = pickItem(mood.keyIds, rnd);
12950
+ const target2 = COMPOSE_KEYS[pickedKeyId2];
12951
+ return {
12952
+ mode: target2.mode,
12953
+ rootShift: target2.rootShift,
12954
+ keyName: target2.name,
12955
+ keyLabel: target2.label,
12956
+ moodLabel: mood.label,
12957
+ description: target2.description
12958
+ };
12959
+ }
12960
+ if (c === "major") {
12961
+ const pickedKeyId2 = pickItem(MAJOR_KEY_IDS, rnd);
12962
+ const target2 = COMPOSE_KEYS[pickedKeyId2];
12963
+ const targetMood2 = COMPOSE_MOOD_GROUPS.find((m) => m.id === target2.moodId);
12964
+ return {
12965
+ mode: "major",
12966
+ rootShift: target2.rootShift,
12967
+ keyName: target2.name,
12968
+ keyLabel: target2.label,
12969
+ moodLabel: targetMood2?.label,
12970
+ description: target2.description
12877
12971
  };
12878
- pattern.push(instruction);
12879
- runStart = bar + 1;
12880
12972
  }
12881
- const variety = new Set(barPatterns.map((p) => JSON.stringify(p))).size;
12973
+ if (c === "minor") {
12974
+ const pickedKeyId2 = pickItem(MINOR_KEY_IDS, rnd);
12975
+ const target2 = COMPOSE_KEYS[pickedKeyId2];
12976
+ const targetMood2 = COMPOSE_MOOD_GROUPS.find((m) => m.id === target2.moodId);
12977
+ return {
12978
+ mode: "minor",
12979
+ rootShift: target2.rootShift,
12980
+ keyName: target2.name,
12981
+ keyLabel: target2.label,
12982
+ moodLabel: targetMood2?.label,
12983
+ description: target2.description
12984
+ };
12985
+ }
12986
+ const allKeyIds = Object.keys(COMPOSE_KEYS);
12987
+ const pickedKeyId = pickItem(allKeyIds, rnd);
12988
+ const target = COMPOSE_KEYS[pickedKeyId];
12989
+ const targetMood = COMPOSE_MOOD_GROUPS.find((m) => m.id === target.moodId);
12882
12990
  return {
12883
- style,
12884
- variety,
12885
- def: { label: `\u4F5C\u66F2\uFF08${DRUM_STYLE_LABELS[style]}\uFF09`, pattern }
12886
- };
12991
+ mode: void 0,
12992
+ // コード進行の長短制約は掛けず従来通りランダム
12993
+ rootShift: target.rootShift,
12994
+ keyName: target.name,
12995
+ keyLabel: target.label,
12996
+ moodLabel: targetMood?.label,
12997
+ description: target.description
12998
+ };
12999
+ };
13000
+ var getComposeKeyDescription = (choice) => {
13001
+ if (!choice || choice === "any") return "\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059";
13002
+ if (choice === "major") return "12\u306E\u9577\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059";
13003
+ if (choice === "minor") return "12\u306E\u77ED\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059";
13004
+ const mood = COMPOSE_MOOD_GROUPS.find((m) => m.id === choice);
13005
+ if (mood) return `${mood.label}\uFF1A${mood.description}`;
13006
+ const target = COMPOSE_KEYS[choice];
13007
+ if (target) return `${target.label}\uFF1A${target.description}`;
13008
+ return "";
12887
13009
  };
12888
13010
 
12889
13011
  // src/compose-metrics.ts
@@ -13315,7 +13437,6 @@ var transposeChordName = (chord, shift) => {
13315
13437
  return names[((pc + normShift) % 12 + 12) % 12];
13316
13438
  });
13317
13439
  };
13318
- var ROOT_SHIFTS = [0, 1, 2, 3, 4, 5, 6, -1, -2, -3, -4, -5];
13319
13440
  var BPM_CHOICES = [
13320
13441
  112,
13321
13442
  120,
@@ -13937,7 +14058,7 @@ var MOTIF_ARCHETYPES = [
13937
14058
  [0, 5, 4, 2, 0]
13938
14059
  // 6度跳躍からの大きな下降
13939
14060
  ];
13940
- var pick2 = (items, rnd) => items[Math.floor(rnd() * items.length)];
14061
+ var pick = (items, rnd) => items[Math.floor(rnd() * items.length)];
13941
14062
  var durationEntropy = (durations) => {
13942
14063
  if (durations.length === 0) return 0;
13943
14064
  const counts = /* @__PURE__ */ new Map();
@@ -14005,7 +14126,7 @@ var leapTarget = (from, tones, rnd) => {
14005
14126
  ...octaves,
14006
14127
  ...near.slice(0, Math.max(2, Math.ceil(near.length / 2)))
14007
14128
  ];
14008
- return pick2(weighted.length > 0 ? weighted : candidates, rnd);
14129
+ return pick(weighted.length > 0 ? weighted : candidates, rnd);
14009
14130
  };
14010
14131
  var landOn = (degrees, scaleIndex) => {
14011
14132
  if (degrees.length === 0) return;
@@ -14034,7 +14155,7 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14034
14155
  const noteCount = slots.length;
14035
14156
  const out = [];
14036
14157
  if (role === "motif" || role === "sequence" || role === "climax" || role === "answer") {
14037
- const shift = (role === "sequence" ? pick2([-2, -1, 1, 2], rnd) : role === "climax" ? 5 : 0) + repeatShift;
14158
+ const shift = (role === "sequence" ? pick([-2, -1, 1, 2], rnd) : role === "climax" ? 5 : 0) + repeatShift;
14038
14159
  if (style.pentatonicMotif) {
14039
14160
  const startPenta = degreeToPenta(startDegree);
14040
14161
  for (let i2 = 0; i2 < noteCount; i2++)
@@ -14100,7 +14221,7 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14100
14221
  }
14101
14222
  return out;
14102
14223
  }
14103
- const span = pick2([2, 3, 4], rnd);
14224
+ const span = pick([2, 3, 4], rnd);
14104
14225
  const dir = style.stepShape === "descend" ? -1 : style.stepShape === "ascend" ? 1 : rnd() < 0.5 ? 1 : -1;
14105
14226
  for (let i2 = 0; i2 < noteCount; i2++) {
14106
14227
  const t = noteCount === 1 ? 0 : i2 / (noteCount - 1);
@@ -14328,7 +14449,7 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
14328
14449
  lastAltered = i2;
14329
14450
  }
14330
14451
  };
14331
- var draw = (options, rnd) => {
14452
+ var draw = (options, resolvedKey, rnd) => {
14332
14453
  const stepsPerBar = options.stepsPerBar;
14333
14454
  const edo = options.edo === 31 ? 31 : 12;
14334
14455
  const scaleStep = (v) => Math.max(1, Math.round(Math.abs(v) * stepsPerBar / BASE_STEPS_PER_BAR)) * Math.sign(v);
@@ -14352,12 +14473,12 @@ var draw = (options, rnd) => {
14352
14473
  const sectionPlan = buildSectionPlan(options.sections ?? DEFAULT_SECTIONS);
14353
14474
  const totalBars = sectionPlan.reduce((sum, s) => sum + s.bars, 0);
14354
14475
  if (rnd() < 0.35) {
14355
- const modType = pick2(
14476
+ const modType = pick(
14356
14477
  ["chorus_up", "chorus_up", "prechorus_down"],
14357
14478
  rnd
14358
14479
  );
14359
14480
  if (modType === "chorus_up") {
14360
- const shift = pick2([1, 1, 2], rnd);
14481
+ const shift = pick([1, 1, 2], rnd);
14361
14482
  let lastChorusIdx = -1;
14362
14483
  for (let i2 = 0; i2 < sectionPlan.length; i2++) {
14363
14484
  if (sectionPlan[i2].kind === "chorus") lastChorusIdx = i2;
@@ -14368,7 +14489,7 @@ var draw = (options, rnd) => {
14368
14489
  }
14369
14490
  }
14370
14491
  } else {
14371
- const shift = pick2([-2, 3], rnd);
14492
+ const shift = pick([-2, 3], rnd);
14372
14493
  for (const s of sectionPlan) {
14373
14494
  if (s.kind === "prechorus") s.keyShift = shift;
14374
14495
  }
@@ -14380,14 +14501,15 @@ var draw = (options, rnd) => {
14380
14501
  barKeyShift[b] = s.keyShift;
14381
14502
  }
14382
14503
  }
14383
- const progA = pick2(SECTION_A_PROGRESSIONS, rnd);
14504
+ 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;
14505
+ const progA = pick(progAPool, rnd);
14384
14506
  const progBPool = SECTION_B_PROGRESSIONS.filter(
14385
14507
  (p) => p.join("|") !== progA.join("|")
14386
14508
  );
14387
- const progB = pick2(progBPool, rnd);
14509
+ const progB = pick(progBPool, rnd);
14388
14510
  const tonic = progA[0].startsWith("Am") ? "Am" : "C";
14389
- const progHalf = pick2(SECTION_A2_DERIVATIONS, rnd)(progA);
14390
- const progFull = pick2(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
14511
+ const progHalf = pick(SECTION_A2_DERIVATIONS, rnd)(progA);
14512
+ const progFull = pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
14391
14513
  const progression = [];
14392
14514
  for (const section of sectionPlan) {
14393
14515
  const base = section.spec.progression === "b" ? progB : progA;
@@ -14405,9 +14527,9 @@ var draw = (options, rnd) => {
14405
14527
  }
14406
14528
  progression.length = totalBars;
14407
14529
  const chordProgression = progression.map((chord, bar) => transposeChordName(chord, barKeyShift[bar])).join("|");
14408
- const chordPattern = pick2(CHORD_PATTERNS, rnd);
14409
- const rootShift = pick2(ROOT_SHIFTS, rnd);
14410
- const bpm = pick2(BPM_CHOICES, rnd);
14530
+ const chordPattern = pick(CHORD_PATTERNS, rnd);
14531
+ const rootShift = resolvedKey.rootShift;
14532
+ const bpm = pick(BPM_CHOICES, rnd);
14411
14533
  const sourceOf = (kind) => kind === "chorus" || kind === "interlude" ? "b" : kind === "prechorus" ? "a2" : "a";
14412
14534
  const units2 = [];
14413
14535
  for (const section of sectionPlan) {
@@ -14456,19 +14578,19 @@ var draw = (options, rnd) => {
14456
14578
  return null;
14457
14579
  };
14458
14580
  const style = {
14459
- groove: pick2(["eighth", "sixteenth"], rnd),
14460
- arcPeriod: pick2([4, 8, 8, 16], rnd),
14461
- arcPhase: pick2([0, 1, 2], rnd),
14581
+ groove: pick(["eighth", "sixteenth"], rnd),
14582
+ arcPeriod: pick([4, 8, 8, 16], rnd),
14583
+ arcPhase: pick([0, 1, 2], rnd),
14462
14584
  arcAmp: 3 + rnd() * 4,
14463
14585
  octaveAffinity: 0.14 + rnd() * 0.2,
14464
14586
  pentatonicMotif: rnd() < 0.55,
14465
- runShape: pick2(["scale", "turn", "broken", "zigzag"], rnd),
14466
- stepShape: pick2(
14587
+ runShape: pick(["scale", "turn", "broken", "zigzag"], rnd),
14588
+ stepShape: pick(
14467
14589
  ["arch", "valley", "ascend", "descend", "wave", "pivot"],
14468
14590
  rnd
14469
14591
  ),
14470
- holdShape: pick2(["long", "third", "neighbor"], rnd),
14471
- cadenceShape: pick2(
14592
+ holdShape: pick(["long", "third", "neighbor"], rnd),
14593
+ cadenceShape: pick(
14472
14594
  ["descend", "five-three-one", "leap-up", "hold-tonic"],
14473
14595
  rnd
14474
14596
  ),
@@ -14478,7 +14600,7 @@ var draw = (options, rnd) => {
14478
14600
  // 全曲に掛けると「どの曲も同じ味付け」になる)。
14479
14601
  chromaticAffinity: rnd() < 0.25 ? 0 : 0.15 + rnd() * 0.45,
14480
14602
  barHeadWeight: rnd() < 0.65 ? 3 : 2,
14481
- bassStyle: pick2(
14603
+ bassStyle: pick(
14482
14604
  [
14483
14605
  "quarter",
14484
14606
  "alternate",
@@ -14499,8 +14621,8 @@ var draw = (options, rnd) => {
14499
14621
  // パッド・保続音・付点2分は「置いただけの音」になりやすく、実際
14500
14622
  // サブメロの音の38%が全音符1つ(1.5音/小節)まで薄くなっていた。
14501
14623
  // それらは終止の小節でだけ使う逃げ道に降格する。
14502
- subStyle: pick2(["harmony", "harmony", "counter"], rnd),
14503
- subInterval: pick2([3, 4, 8, 9], rnd)
14624
+ subStyle: pick(["harmony", "harmony", "counter"], rnd),
14625
+ subInterval: pick([3, 4, 8, 9], rnd)
14504
14626
  };
14505
14627
  const motifPool = groovyCells(MOTIF_CELLS, style.groove, rnd);
14506
14628
  const targetNotesPerBar = 5.5 + rnd() * 2.5;
@@ -14517,9 +14639,9 @@ var draw = (options, rnd) => {
14517
14639
  };
14518
14640
  const cellDistance = (c) => Math.abs(cellNotes(c) - targetNotesPerBar) + Math.abs(cellRest(c) - targetRestRatio) * 8;
14519
14641
  const pickCell = (pool) => {
14520
- let best = pick2(pool, rnd);
14642
+ let best = pick(pool, rnd);
14521
14643
  for (let i2 = 0; i2 < 2; i2++) {
14522
- const c = pick2(pool, rnd);
14644
+ const c = pick(pool, rnd);
14523
14645
  if (cellDistance(c) < cellDistance(best)) best = c;
14524
14646
  }
14525
14647
  return best;
@@ -14542,7 +14664,7 @@ var draw = (options, rnd) => {
14542
14664
  const motifBVar = pickCell(
14543
14665
  motifPool.filter((c) => c !== motifB && c !== motifB2)
14544
14666
  );
14545
- const buildUpCell = pick2(
14667
+ const buildUpCell = pick(
14546
14668
  [
14547
14669
  {
14548
14670
  value: [EIGHTH, EIGHTH, EIGHTH, EIGHTH, QUARTER, -QUARTER],
@@ -14563,7 +14685,7 @@ var draw = (options, rnd) => {
14563
14685
  ],
14564
14686
  rnd
14565
14687
  );
14566
- const counterRhythm = pick2(
14688
+ const counterRhythm = pick(
14567
14689
  groovyCells(
14568
14690
  RHYTHM_CELLS.filter((c) => c.density === "medium"),
14569
14691
  style.groove,
@@ -14571,7 +14693,7 @@ var draw = (options, rnd) => {
14571
14693
  ),
14572
14694
  rnd
14573
14695
  ).value;
14574
- const breathCell = pick2(
14696
+ const breathCell = pick(
14575
14697
  RHYTHM_CELLS.filter(
14576
14698
  (c) => c.density === "sparse" && c.value.some((v) => v < 0)
14577
14699
  ),
@@ -14608,14 +14730,14 @@ var draw = (options, rnd) => {
14608
14730
  barRhythms.push(scaleCell(cell.value));
14609
14731
  }
14610
14732
  const motifNoteCount = motifCell.value.filter((v) => v > 0).length + motifCell2.value.filter((v) => v > 0).length;
14611
- const archetype = pick2(MOTIF_ARCHETYPES, rnd);
14733
+ const archetype = pick(MOTIF_ARCHETYPES, rnd);
14612
14734
  const motifContour = [];
14613
14735
  let baseDegree = 0;
14614
14736
  for (let i2 = 0; i2 < motifNoteCount; i2++) {
14615
14737
  const idx = i2 % archetype.length;
14616
14738
  if (i2 > 0 && idx === 0) {
14617
14739
  const lastVal = motifContour[i2 - 1];
14618
- const connectShift = pick2([-1, 0, 1], rnd);
14740
+ const connectShift = pick([-1, 0, 1], rnd);
14619
14741
  baseDegree = lastVal + connectShift - archetype[0];
14620
14742
  }
14621
14743
  motifContour.push(baseDegree + archetype[idx]);
@@ -14633,12 +14755,12 @@ var draw = (options, rnd) => {
14633
14755
  let intervals = 0;
14634
14756
  let chromaticNotes = 0;
14635
14757
  let sungBars = 0;
14636
- let prevSemi = pick2([60, 64, 65, 67, 69, 72, 74, 76], rnd);
14758
+ let prevSemi = pick([60, 64, 65, 67, 69, 72, 74, 76], rnd);
14637
14759
  const pickFigure = (gapSteps, scale) => {
14638
14760
  const fits = ANSWER_FIGURES.filter(
14639
14761
  (f) => f.reduce((sum, v) => sum + scale(Math.abs(v)), 0) <= gapSteps
14640
14762
  );
14641
- return fits.length === 0 ? null : pick2(fits, rnd);
14763
+ return fits.length === 0 ? null : pick(fits, rnd);
14642
14764
  };
14643
14765
  const melodyGaps = (slots) => {
14644
14766
  const gaps = [];
@@ -14679,7 +14801,7 @@ var draw = (options, rnd) => {
14679
14801
  ).semi;
14680
14802
  const contourOffset = barInUnit(bar) === 0 ? 0 : barRhythms[bar - 1].filter((v) => v > 0).length;
14681
14803
  const prevRole = bar > 0 ? barRoles[bar - 1] : null;
14682
- const repeatShift = role === "motif" && (prevRole === "motif" || prevRole === "sequence" || prevRole === "climax") ? pick2([-2, -1, 1, 2], rnd) : 0;
14804
+ const repeatShift = role === "motif" && (prevRole === "motif" || prevRole === "sequence" || prevRole === "climax") ? pick([-2, -1, 1, 2], rnd) : 0;
14683
14805
  const degrees = barDegrees(
14684
14806
  role,
14685
14807
  slots,
@@ -14975,6 +15097,9 @@ var draw = (options, rnd) => {
14975
15097
  chordProgression,
14976
15098
  chordPattern,
14977
15099
  rootShift,
15100
+ keyName: resolvedKey.keyName,
15101
+ keyLabel: resolvedKey.keyLabel,
15102
+ moodLabel: resolvedKey.moodLabel,
14978
15103
  bpm,
14979
15104
  sections: sectionPlan,
14980
15105
  bars: totalBars,
@@ -15089,12 +15214,13 @@ var composeSong = (options) => {
15089
15214
  const rnd = options.random ?? Math.random;
15090
15215
  const recent = options.recent ?? [];
15091
15216
  const count = Math.max(1, options.drawCount ?? DRAW_COUNT);
15217
+ const resolvedKey = resolveComposeKey(options.baseKey, rnd);
15092
15218
  let best = null;
15093
15219
  let bestScore = Number.NEGATIVE_INFINITY;
15094
15220
  let bestIsValid = false;
15095
15221
  let rejected = 0;
15096
15222
  for (let attempt = 1; attempt <= count; attempt++) {
15097
- const d = draw(options, rnd);
15223
+ const d = draw(options, resolvedKey, rnd);
15098
15224
  const { stats, ok } = evaluate(d, recent);
15099
15225
  if (!ok) rejected++;
15100
15226
  const better = ok === bestIsValid ? stats.score > bestScore : ok;
@@ -15104,10 +15230,13 @@ var composeSong = (options) => {
15104
15230
  best = {
15105
15231
  // ドラムは勝った候補にだけ後から付ける(メロディに依存しないので
15106
15232
  // 候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
15107
- drums: null,
15233
+ drum: "",
15108
15234
  chordProgression: d.chordProgression,
15109
15235
  chordPattern: d.chordPattern,
15110
15236
  rootShift: d.rootShift,
15237
+ keyName: d.keyName,
15238
+ keyLabel: d.keyLabel,
15239
+ moodLabel: d.moodLabel,
15111
15240
  bpm: d.bpm,
15112
15241
  sections: d.sections,
15113
15242
  bars: d.bars,
@@ -15120,32 +15249,14 @@ var composeSong = (options) => {
15120
15249
  const result = best;
15121
15250
  result.stats.attempts = count;
15122
15251
  result.stats.rejected = rejected;
15123
- const levels = new Array(result.bars).fill(1);
15124
- const crashBars = [];
15125
- const fillBars = [];
15126
- for (const section of result.sections) {
15127
- for (let i2 = 0; i2 < section.bars; i2++)
15128
- levels[section.startBar + i2] = section.spec.drumLevel;
15129
- crashBars.push(section.startBar + 1);
15130
- const fill = section.startBar + section.bars - 1;
15131
- if (fill > 0) fillBars.push(fill);
15132
- }
15133
- result.drums = composeDrumPattern({
15134
- bars: result.bars,
15135
- stepsPerBar: options.stepsPerBar,
15136
- rnd,
15137
- style: pickDrumStyle(result, rnd),
15138
- levels,
15139
- crashBars,
15140
- fillBars
15141
- });
15252
+ result.drum = pickBuiltinDrum(result, rnd);
15142
15253
  return result;
15143
15254
  };
15144
- var pickDrumStyle = (song, rnd) => {
15255
+ var pickBuiltinDrum = (song, rnd) => {
15145
15256
  const eighth = BASE_STEPS_PER_BAR / 8;
15146
15257
  const short = song.melody.filter((n) => n.durationSteps <= eighth).length / Math.max(1, song.melody.length);
15147
- 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"];
15148
- return pick2(pool, rnd);
15258
+ 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"];
15259
+ return pick(pool, rnd);
15149
15260
  };
15150
15261
  var LYRIC_WORDS = [
15151
15262
  "\u3042\u3055",
@@ -15193,7 +15304,7 @@ var composeLyrics = (melody, options) => {
15193
15304
  const quarter = stepsPerBar / 4;
15194
15305
  let buffer = [];
15195
15306
  const nextKana = () => {
15196
- if (buffer.length === 0) buffer = [...pick2(LYRIC_WORDS, rnd)];
15307
+ if (buffer.length === 0) buffer = [...pick(LYRIC_WORDS, rnd)];
15197
15308
  return buffer.shift();
15198
15309
  };
15199
15310
  const out = [];
@@ -15558,6 +15669,56 @@ var buildUI = (target, options) => {
15558
15669
  <span class="dtm-grow"></span>
15559
15670
  <span class="dtm-hint" data-dtm="compose-sections-len"></span>
15560
15671
  </div>
15672
+ <div class="dtm-row ${showCompose ? "" : "dtm-hidden"}" data-dtm="compose-key-row">
15673
+ <span class="dtm-label">\u30D9\u30FC\u30B9\u8ABF</span>
15674
+ <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">
15675
+ <option value="any" title="\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057</option>
15676
+ <optgroup label="\u57FA\u672C">
15677
+ <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>
15678
+ <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>
15679
+ </optgroup>
15680
+ <optgroup label="\u96F0\u56F2\u6C17\u304B\u3089\u9078\u3076\uFF08\u62BD\u9078\uFF09">
15681
+ <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>
15682
+ <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>
15683
+ <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>
15684
+ <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>
15685
+ <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>
15686
+ <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>
15687
+ <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>
15688
+ <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>
15689
+ </optgroup>
15690
+ <optgroup label="\u9577\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
15691
+ <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>
15692
+ <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>
15693
+ <option value="key_D" title="\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0">\u30CB\u9577\u8ABF (D)</option>
15694
+ <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>
15695
+ <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>
15696
+ <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>
15697
+ <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>
15698
+ <option value="key_G" title="\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F">\u30C8\u9577\u8ABF (G)</option>
15699
+ <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>
15700
+ <option value="key_A" title="\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D">\u30A4\u9577\u8ABF (A)</option>
15701
+ <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>
15702
+ <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>
15703
+ </optgroup>
15704
+ <optgroup label="\u77ED\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
15705
+ <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>
15706
+ <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>
15707
+ <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>
15708
+ <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>
15709
+ <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>
15710
+ <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>
15711
+ <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>
15712
+ <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>
15713
+ <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>
15714
+ <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>
15715
+ <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>
15716
+ <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>
15717
+ </optgroup>
15718
+ </select>
15719
+ <span class="dtm-grow"></span>
15720
+ <span class="dtm-hint" data-dtm="compose-key-hint"></span>
15721
+ </div>
15561
15722
  <div class="dtm-row">
15562
15723
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
15563
15724
  <select class="dtm-select" data-dtm="shift-select">
@@ -15753,6 +15914,8 @@ var buildUI = (target, options) => {
15753
15914
  macroCompose: sel("macro-compose"),
15754
15915
  composeSections: sel("compose-sections"),
15755
15916
  composeSectionsLen: sel("compose-sections-len"),
15917
+ composeKey: sel("compose-key"),
15918
+ composeKeyHint: sel("compose-key-hint"),
15756
15919
  macroComposeVocal: sel("macro-compose-vocal"),
15757
15920
  macroComposeInfo: sel("macro-compose-info"),
15758
15921
  macroClear: sel("macro-clear"),
@@ -21648,6 +21811,16 @@ var mountDAW = (target, options = {}) => {
21648
21811
  };
21649
21812
  refs.composeSections.addEventListener("change", updateComposeSectionsLen);
21650
21813
  updateComposeSectionsLen();
21814
+ const updateComposeKeyHint = () => {
21815
+ if (!refs.composeKey || !refs.composeKeyHint) return;
21816
+ const desc = getComposeKeyDescription(refs.composeKey.value);
21817
+ refs.composeKeyHint.textContent = desc;
21818
+ refs.composeKeyHint.title = desc;
21819
+ };
21820
+ if (refs.composeKey) {
21821
+ refs.composeKey.addEventListener("change", updateComposeKeyHint);
21822
+ updateComposeKeyHint();
21823
+ }
21651
21824
  const runCompose = (withVocal) => {
21652
21825
  stop();
21653
21826
  overlayDuring(() => {
@@ -21655,6 +21828,7 @@ var mountDAW = (target, options = {}) => {
21655
21828
  stepsPerBar: renderConfig.stepsPerBar,
21656
21829
  edo: renderConfig.edo,
21657
21830
  sections: selectedComposeSections(),
21831
+ baseKey: refs.composeKey?.value ?? "any",
21658
21832
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
21659
21833
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
21660
21834
  recent: recentComposeFingerprints
@@ -21727,7 +21901,10 @@ var mountDAW = (target, options = {}) => {
21727
21901
  }
21728
21902
  }
21729
21903
  setBpm(song.bpm);
21730
- applyComposedDrumPattern(song.drums.def);
21904
+ currentDrumPattern = song.drum;
21905
+ refs.drumSelect.value = song.drum;
21906
+ options.onDrumChange?.(song.drum);
21907
+ applyDrumPatternFont(song.drum);
21731
21908
  for (const t of bassTracksForCompose()) {
21732
21909
  t.trackCompression = AUTO_ROLE_MIX.bass.compression;
21733
21910
  options.onTrackCompressionChange?.(t.config.id, t.trackCompression);
@@ -21746,6 +21923,10 @@ var mountDAW = (target, options = {}) => {
21746
21923
  fireLyricsChange(melodyTrack);
21747
21924
  }
21748
21925
  }
21926
+ if (refs.composeKeyHint) {
21927
+ refs.composeKeyHint.textContent = `${song.keyLabel} \u3067\u4F5C\u6210`;
21928
+ refs.composeKeyHint.title = `${song.keyLabel}${song.moodLabel ? `\uFF08${song.moodLabel}\uFF09` : ""}`;
21929
+ }
21749
21930
  playStartStep = 0;
21750
21931
  redrawAll();
21751
21932
  updateTrackPanel();
@@ -22096,9 +22277,6 @@ var mountDAW = (target, options = {}) => {
22096
22277
  extractedMidiDrum = patternDef.pattern;
22097
22278
  installDrumPattern("_extracted_midi", patternDef);
22098
22279
  };
22099
- const applyComposedDrumPattern = (patternDef) => {
22100
- installDrumPattern("_composed", patternDef);
22101
- };
22102
22280
  const wireMidi = () => {
22103
22281
  refs.midiInput.addEventListener("change", async () => {
22104
22282
  const file = refs.midiInput.files?.[0];
@@ -24618,7 +24796,7 @@ var createDtmStudio = async (options = {}) => {
24618
24796
  await loadInstrument(key);
24619
24797
  }
24620
24798
  };
24621
- void Promise.all([
24799
+ const presetPromise = Promise.all([
24622
24800
  loadPreset(
24623
24801
  playerPreset,
24624
24802
  loadTrackIds,
@@ -24658,25 +24836,29 @@ var createDtmStudio = async (options = {}) => {
24658
24836
  duration: e.duration
24659
24837
  });
24660
24838
  };
24839
+ const { onResumeAudio: userOnResumeAudio, ...playerOpts } = opts;
24661
24840
  const player = mountMmlPlayer(target, mml, {
24841
+ ...playerOpts,
24662
24842
  getAudioTime: () => audioCtx.currentTime,
24663
24843
  onResumeAudio: async () => {
24664
24844
  await resumeAudio();
24845
+ await presetPromise;
24665
24846
  if (meta.drum) {
24666
24847
  await loadRequiredDrums(
24667
24848
  getDrumPatternKeys(
24668
24849
  meta.drum,
24669
- resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
24850
+ resolveDrumPatterns(
24851
+ playerOpts.drumPatterns ?? options.drumPatterns
24852
+ )
24670
24853
  ),
24671
24854
  meta.drumFont || "FluidR3_GM_sf2_file:0"
24672
24855
  );
24673
24856
  }
24674
- await opts.onResumeAudio?.();
24857
+ await userOnResumeAudio?.();
24675
24858
  },
24676
24859
  onPlayNote: playPlayerNote,
24677
24860
  onPlayDrum: playDrum,
24678
- singingVoices,
24679
- ...opts
24861
+ singingVoices
24680
24862
  });
24681
24863
  mountedPlayers.push(player);
24682
24864
  const destroy = () => {
@@ -24709,7 +24891,7 @@ var createDtmStudio = async (options = {}) => {
24709
24891
  await loadInstrument(key);
24710
24892
  }
24711
24893
  };
24712
- void Promise.all([
24894
+ const presetPromise = Promise.all([
24713
24895
  loadPreset(
24714
24896
  playerPreset,
24715
24897
  loadTrackIds,
@@ -24749,8 +24931,9 @@ var createDtmStudio = async (options = {}) => {
24749
24931
  duration: e.duration
24750
24932
  });
24751
24933
  };
24934
+ const { onResumeAudio: userOnResumeAudio, ...playOpts } = opts;
24752
24935
  return playMML(mml, {
24753
- ...opts,
24936
+ ...playOpts,
24754
24937
  audioContext: audioCtx,
24755
24938
  destination: masterGain,
24756
24939
  synth: false,
@@ -24758,15 +24941,18 @@ var createDtmStudio = async (options = {}) => {
24758
24941
  onPlayDrum: playDrum,
24759
24942
  onResumeAudio: async () => {
24760
24943
  await resumeAudio();
24944
+ await presetPromise;
24761
24945
  if (meta.drum) {
24762
24946
  await loadRequiredDrums(
24763
24947
  getDrumPatternKeys(
24764
24948
  meta.drum,
24765
- resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
24949
+ resolveDrumPatterns(
24950
+ playOpts.drumPatterns ?? options.drumPatterns
24951
+ )
24766
24952
  )
24767
24953
  );
24768
24954
  }
24769
- await opts.onResumeAudio?.();
24955
+ await userOnResumeAudio?.();
24770
24956
  }
24771
24957
  });
24772
24958
  };
@@ -24793,7 +24979,7 @@ var createDtmStudio = async (options = {}) => {
24793
24979
  await loadInstrument(key);
24794
24980
  }
24795
24981
  };
24796
- void Promise.all([
24982
+ const presetPromise = Promise.all([
24797
24983
  loadPreset(
24798
24984
  playerPreset,
24799
24985
  loadTrackIds,
@@ -24833,8 +25019,9 @@ var createDtmStudio = async (options = {}) => {
24833
25019
  duration: e.duration
24834
25020
  });
24835
25021
  };
25022
+ const { onResumeAudio: userOnResumeAudio, ...playOpts } = opts;
24836
25023
  return playSingingMML(mml, {
24837
- ...opts,
25024
+ ...playOpts,
24838
25025
  audioContext: audioCtx,
24839
25026
  destination: masterGain,
24840
25027
  synth: false,
@@ -24843,15 +25030,18 @@ var createDtmStudio = async (options = {}) => {
24843
25030
  onPlayDrum: playDrum,
24844
25031
  onResumeAudio: async () => {
24845
25032
  await resumeAudio();
25033
+ await presetPromise;
24846
25034
  if (meta.drum) {
24847
25035
  await loadRequiredDrums(
24848
25036
  getDrumPatternKeys(
24849
25037
  meta.drum,
24850
- resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
25038
+ resolveDrumPatterns(
25039
+ playOpts.drumPatterns ?? options.drumPatterns
25040
+ )
24851
25041
  )
24852
25042
  );
24853
25043
  }
24854
- await opts.onResumeAudio?.();
25044
+ await userOnResumeAudio?.();
24855
25045
  }
24856
25046
  });
24857
25047
  };
@@ -24920,7 +25110,7 @@ var createDtmStudio = async (options = {}) => {
24920
25110
  velocity: p.velocity
24921
25111
  }));
24922
25112
  const playerPreset = defaultPreset;
24923
- void loadPreset(playerPreset, ["chord"]);
25113
+ const presetPromise = loadPreset(playerPreset, ["chord"]);
24924
25114
  const playPlayerNote = (e) => {
24925
25115
  const sfInst = resolveSoundFont(playerPreset, "chord");
24926
25116
  if (!sfInst) return;
@@ -24936,16 +25126,21 @@ var createDtmStudio = async (options = {}) => {
24936
25126
  duration: e.duration
24937
25127
  });
24938
25128
  };
25129
+ const { onResumeAudio: userOnResumeAudio, ...chordOpts } = opts;
24939
25130
  return playPlacements(placements, {
24940
- ...opts,
25131
+ ...chordOpts,
24941
25132
  audioContext: audioCtx,
24942
25133
  destination: masterGain,
24943
25134
  synth: false,
24944
25135
  onPlayNote: playPlayerNote,
24945
25136
  onPlayDrum: playDrum,
24946
- onResumeAudio: resumeAudio,
25137
+ onResumeAudio: async () => {
25138
+ await resumeAudio();
25139
+ await presetPromise;
25140
+ await userOnResumeAudio?.();
25141
+ },
24947
25142
  bpm,
24948
- metaVolume: opts.volume ?? 100
25143
+ metaVolume: chordOpts.volume ?? 100
24949
25144
  });
24950
25145
  };
24951
25146
  const mountChordPlayerInstance = (target, chords, options2 = {}) => {
@@ -25072,6 +25267,8 @@ var createDtmStudio = async (options = {}) => {
25072
25267
  A4_UNITS,
25073
25268
  BREATH_MARK,
25074
25269
  CENTS_PER_UNIT,
25270
+ COMPOSE_KEYS,
25271
+ COMPOSE_MOOD_GROUPS,
25075
25272
  CORPUS_BANDS,
25076
25273
  CORPUS_SIZE,
25077
25274
  DAW_CSS,
@@ -25098,8 +25295,10 @@ var createDtmStudio = async (options = {}) => {
25098
25295
  KOE_VOICEBANK_LABELS,
25099
25296
  KOE_VOICEBANK_TERMS,
25100
25297
  LinkedList,
25298
+ MAJOR_KEY_IDS,
25101
25299
  MAX_VOCAL_VOLUME,
25102
25300
  MICRO_STEP,
25301
+ MINOR_KEY_IDS,
25103
25302
  MMLCore,
25104
25303
  MML_END_MARKER,
25105
25304
  MidiSearchClient,
@@ -25168,6 +25367,7 @@ var createDtmStudio = async (options = {}) => {
25168
25367
  formatMmlMeta,
25169
25368
  freqFromPitch,
25170
25369
  generateRandomPattern,
25370
+ getComposeKeyDescription,
25171
25371
  getDrumPatternKeys,
25172
25372
  getMidiBPM,
25173
25373
  icon,
@@ -25197,6 +25397,7 @@ var createDtmStudio = async (options = {}) => {
25197
25397
  playNote,
25198
25398
  playPlacements,
25199
25399
  playSingingMML,
25400
+ resolveComposeKey,
25200
25401
  resolveDrumPattern,
25201
25402
  resolveLoopPoint,
25202
25403
  semitonesToUnits,