@onjmin/dtm 2.1.4 → 2.1.5

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
@@ -4728,6 +4728,7 @@ var createSequencer = (options) => {
4728
4728
  let drumCursor = 0;
4729
4729
  let lastDrumStep = Number.NEGATIVE_INFINITY;
4730
4730
  let lastPlayStep = 0;
4731
+ let maxTimelineEndSec = 0;
4731
4732
  const secondsPerStep = () => 60 / options.getBpm() / STEPS_PER_BEAT;
4732
4733
  const getWrappedPlayStep = (time, sps) => {
4733
4734
  if (!isLooping || loopDurationSec <= 0 || time < loopEndSec) {
@@ -4754,6 +4755,7 @@ var createSequencer = (options) => {
4754
4755
  }
4755
4756
  const startLimit = isLooping ? Math.min(fromStep, loopStartStep) : fromStep;
4756
4757
  let maxEndStep = 0;
4758
+ maxTimelineEndSec = 0;
4757
4759
  for (const track of options.getTracks()) {
4758
4760
  trackVolumeMap.set(track.id, track.volume);
4759
4761
  for (const note of track.notes) {
@@ -4762,6 +4764,7 @@ var createSequencer = (options) => {
4762
4764
  const when = relativeStart * sps;
4763
4765
  const duration = note.durationSteps * sps;
4764
4766
  maxEndStep = Math.max(maxEndStep, note.startStep + note.durationSteps);
4767
+ maxTimelineEndSec = Math.max(maxTimelineEndSec, when + duration);
4765
4768
  timeline.push({
4766
4769
  trackId: track.id,
4767
4770
  pitch: note.pitchUnits,
@@ -4887,10 +4890,7 @@ var createSequencer = (options) => {
4887
4890
  lastPlayStep = currentStep2;
4888
4891
  }
4889
4892
  if (!isLooping) {
4890
- const last = timeline[timeline.length - 1];
4891
- const lastWhen = last?.when ?? 0;
4892
- const lastDuration = last?.duration ?? 0;
4893
- if (nowIndex >= timeline.length && time > lastWhen + lastDuration + 0.1) {
4893
+ if (nowIndex >= timeline.length && time > maxTimelineEndSec + 0.1) {
4894
4894
  stop();
4895
4895
  options.onEnd(false);
4896
4896
  }
@@ -8222,6 +8222,21 @@ var DAW_CSS = `
8222
8222
  .dtm-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
8223
8223
  .dtm-track-body { display: flex; flex-direction: column; gap: 10px; }
8224
8224
 
8225
+ /* \u2500\u2500\u2500 \u81EA\u52D5\u4F5C\u66F2\u30B3\u30F3\u30C6\u30CA\uFF08\u30DE\u30AF\u30ED\u30D1\u30CD\u30EB\u5185\u306E\u5C02\u7528\u30AB\u30FC\u30C9\u98A8\u30B0\u30EB\u30FC\u30D7\u67A0\uFF09 \u2500\u2500\u2500 */
8226
+ .dtm-compose-container {
8227
+ background: var(--dtm-deep);
8228
+ border: 2px solid var(--c-black);
8229
+ box-shadow:
8230
+ inset 0 0 0 1px var(--dtm-border2),
8231
+ 0 0 0 1px var(--dtm-success),
8232
+ 3px 3px 0 var(--c-black);
8233
+ padding: 10px 12px 12px;
8234
+ display: flex;
8235
+ flex-direction: column;
8236
+ gap: 10px;
8237
+ margin-bottom: 6px;
8238
+ }
8239
+
8225
8240
  /* \u2500\u2500\u2500 \u30A2\u30AF\u30C6\u30A3\u30D6\u30C8\u30E9\u30C3\u30AF\u8272\uFF08\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A\u30D1\u30CD\u30EB\u306E\u5DE6\u7AEF\u30E9\u30A4\u30F3\uFF09 \u2500\u2500\u2500 */
8226
8241
  .dtm-panel--track > summary::before,
8227
8242
  .dtm-panel--track[open] > summary::before {
@@ -13016,11 +13031,25 @@ var densityFeatures = (notes, opts) => {
13016
13031
  shortNoteRatio: short / notes.length
13017
13032
  };
13018
13033
  };
13019
- var tensionFeatures = (barTension) => {
13020
- if (barTension.length < 16) return { rise: 0, resolve: 0 };
13034
+ var tensionFeatures = (barTension, opts) => {
13035
+ if (barTension.length < 8) return { rise: 0, resolve: 0 };
13021
13036
  const mean2 = (xs) => xs.reduce((a2, b2) => a2 + b2, 0) / Math.max(1, xs.length);
13022
- const a = mean2(barTension.slice(0, 4));
13023
- const b = mean2(barTension.slice(8, 12));
13037
+ let a;
13038
+ let b;
13039
+ if (opts?.verseBars && opts?.chorusBars && opts.verseBars.length > 0 && opts.chorusBars.length > 0) {
13040
+ a = mean2(opts.verseBars.map((i2) => barTension[i2] ?? 0));
13041
+ b = mean2(opts.chorusBars.map((i2) => barTension[i2] ?? 0));
13042
+ } else if (barTension.length >= 16 && barTension.length <= 24) {
13043
+ a = mean2(barTension.slice(0, 4));
13044
+ b = mean2(barTension.slice(8, 12));
13045
+ } else {
13046
+ const q1Start = Math.floor(barTension.length * 0.15);
13047
+ const q1End = Math.max(q1Start + 2, Math.floor(barTension.length * 0.35));
13048
+ const q3Start = Math.floor(barTension.length * 0.55);
13049
+ const q3End = Math.max(q3Start + 2, Math.floor(barTension.length * 0.75));
13050
+ a = mean2(barTension.slice(q1Start, q1End));
13051
+ b = mean2(barTension.slice(q3Start, q3End));
13052
+ }
13024
13053
  const all = mean2(barTension);
13025
13054
  const last = barTension[barTension.length - 1];
13026
13055
  const rise = Math.max(0, Math.min(1, (b - a) / 0.5));
@@ -13067,6 +13096,8 @@ var SECTION_LABELS = {
13067
13096
  verse: "A\u30E1\u30ED",
13068
13097
  prechorus: "B\u30E1\u30ED",
13069
13098
  chorus: "\u30B5\u30D3",
13099
+ bridge: "C\u30E1\u30ED",
13100
+ drop_chorus: "\u843D\u3061\u30B5\u30D3",
13070
13101
  interlude: "\u9593\u594F",
13071
13102
  outro: "\u30A2\u30A6\u30C8\u30ED"
13072
13103
  };
@@ -13075,6 +13106,8 @@ var SECTION_ORDER = [
13075
13106
  "verse",
13076
13107
  "prechorus",
13077
13108
  "chorus",
13109
+ "bridge",
13110
+ "drop_chorus",
13078
13111
  "interlude",
13079
13112
  "outro"
13080
13113
  ];
@@ -13126,6 +13159,35 @@ var SECTION_SPECS = {
13126
13159
  // 主音へ全終止
13127
13160
  progression: "b"
13128
13161
  },
13162
+ // Cメロ。Verse/Pre chorusとは違うメロディで、ラストのサビ前に緊張感を持たせる。
13163
+ // 2番のサビの後に来ることが多い(参考: ONLIVE Studio blog)。
13164
+ // 専用のコード進行 "c" を持ち、AメロともBメロとも雰囲気が違う。
13165
+ bridge: {
13166
+ bars: 4,
13167
+ melody: true,
13168
+ registerShift: 2,
13169
+ density: 0.9,
13170
+ drumLevel: 1,
13171
+ landing: 1,
13172
+ // 解決しない音で止めてラスサビへ渡す
13173
+ progression: "c"
13174
+ },
13175
+ // 落ちサビ。サビのメロディを伴奏控えめに歌う。J-POPの王道パターン。
13176
+ // density と drumLevel だけ下げ、メロディはサビと同じものを使う。
13177
+ drop_chorus: {
13178
+ bars: 4,
13179
+ melody: true,
13180
+ registerShift: 4,
13181
+ // サビと同じ高さ
13182
+ density: 0.6,
13183
+ // 薄い(ここが「落ち」の実体)
13184
+ drumLevel: 0,
13185
+ // ドラム控えめ
13186
+ landing: 0,
13187
+ // 主音へ全終止
13188
+ progression: "b"
13189
+ // サビと同じ進行
13190
+ },
13129
13191
  interlude: {
13130
13192
  bars: 4,
13131
13193
  melody: false,
@@ -13145,18 +13207,120 @@ var SECTION_SPECS = {
13145
13207
  progression: "a"
13146
13208
  }
13147
13209
  };
13148
- var buildSectionPlan = (kinds) => {
13149
- const wanted = kinds.length > 0 ? kinds : DEFAULT_SECTIONS;
13150
- const ordered = SECTION_ORDER.filter((k) => wanted.includes(k));
13210
+ var STRUCTURE_TEMPLATES = [
13211
+ // 1コーラス(現行デフォルト、短い曲・初心者向け)
13212
+ {
13213
+ name: "1chorus",
13214
+ label: "1\u30B3\u30FC\u30E9\u30B9",
13215
+ plan: ["intro", "verse", "prechorus", "chorus"]
13216
+ },
13217
+ // JPOP王道(マリーゴールド型)
13218
+ // イントロ→Aメロ→Aメロ→Bメロ→サビ→Aメロ→Bメロ→サビ→Cメロ→間奏→サビ→アウトロ
13219
+ {
13220
+ name: "jpop_standard",
13221
+ label: "JPOP\u738B\u9053",
13222
+ plan: [
13223
+ "intro",
13224
+ "verse",
13225
+ "verse",
13226
+ "prechorus",
13227
+ "chorus",
13228
+ "verse",
13229
+ "prechorus",
13230
+ "chorus",
13231
+ "bridge",
13232
+ "interlude",
13233
+ "chorus",
13234
+ "outro"
13235
+ ]
13236
+ },
13237
+ // 落ちサビ入り(J-POP王道の変形。ラスサビ前に落ちサビ)
13238
+ {
13239
+ name: "jpop_drop",
13240
+ label: "\u843D\u3061\u30B5\u30D3\u5165\u308A",
13241
+ plan: [
13242
+ "intro",
13243
+ "verse",
13244
+ "verse",
13245
+ "prechorus",
13246
+ "chorus",
13247
+ "verse",
13248
+ "prechorus",
13249
+ "chorus",
13250
+ "bridge",
13251
+ "drop_chorus",
13252
+ "chorus",
13253
+ "outro"
13254
+ ]
13255
+ },
13256
+ // ボカロ王道(短め高速、Aメロ繰り返しなし)
13257
+ {
13258
+ name: "vocaloid",
13259
+ label: "\u30DC\u30AB\u30ED\u738B\u9053",
13260
+ plan: [
13261
+ "intro",
13262
+ "verse",
13263
+ "prechorus",
13264
+ "chorus",
13265
+ "verse",
13266
+ "prechorus",
13267
+ "chorus",
13268
+ "bridge",
13269
+ "chorus",
13270
+ "outro"
13271
+ ]
13272
+ },
13273
+ // Verse-Chorus形式(Bメロなし、洋楽的)
13274
+ {
13275
+ name: "verse_chorus",
13276
+ label: "Verse-Chorus",
13277
+ plan: [
13278
+ "intro",
13279
+ "verse",
13280
+ "chorus",
13281
+ "verse",
13282
+ "chorus",
13283
+ "bridge",
13284
+ "chorus",
13285
+ "outro"
13286
+ ]
13287
+ }
13288
+ ];
13289
+ var buildSectionPlan = (kinds, templateName) => {
13290
+ let ordered;
13291
+ if (templateName) {
13292
+ const tmpl = STRUCTURE_TEMPLATES.find((t) => t.name === templateName);
13293
+ ordered = tmpl ? tmpl.plan : DEFAULT_SECTIONS;
13294
+ } else {
13295
+ const wanted = kinds.length > 0 ? kinds : DEFAULT_SECTIONS;
13296
+ ordered = SECTION_ORDER.filter((k) => wanted.includes(k));
13297
+ }
13151
13298
  const plan = [];
13152
13299
  let bar = 0;
13300
+ const seen = /* @__PURE__ */ new Map();
13153
13301
  for (const kind of ordered) {
13154
13302
  const spec = SECTION_SPECS[kind];
13155
- plan.push({ kind, startBar: bar, bars: spec.bars, spec, keyShift: 0 });
13303
+ const count = seen.get(kind) ?? 0;
13304
+ plan.push({
13305
+ kind,
13306
+ startBar: bar,
13307
+ bars: spec.bars,
13308
+ spec,
13309
+ keyShift: 0,
13310
+ restatement: count > 0
13311
+ });
13312
+ seen.set(kind, count + 1);
13156
13313
  bar += spec.bars;
13157
13314
  }
13158
13315
  return plan;
13159
13316
  };
13317
+ var sectionAt = (plan, bar) => {
13318
+ for (const section of plan) {
13319
+ if (bar >= section.startBar && bar < section.startBar + section.bars)
13320
+ return section;
13321
+ }
13322
+ return plan[plan.length - 1];
13323
+ };
13160
13324
 
13161
13325
  // src/compose.ts
13162
13326
  var STEP_SEMITONES = 2;
@@ -13442,6 +13606,16 @@ var SECTION_A_PROGRESSIONS = [
13442
13606
  ["Am", "Dm7", "Bb", "E7"]
13443
13607
  // bVI からドミナントへ
13444
13608
  ];
13609
+ var SECTION_C_PROGRESSIONS = [
13610
+ ["Am", "Em", "F", "C"],
13611
+ ["Dm7", "Em7", "FM7", "G7"],
13612
+ ["Am7", "Dm7", "G7", "CM7"],
13613
+ ["F", "G", "Am", "G"],
13614
+ ["Dm7", "G7", "Em7", "Am"],
13615
+ ["FM7", "Em7", "Am7", "Dm7"],
13616
+ ["Am", "G", "FM7", "E7"],
13617
+ ["C", "Am", "FM7", "G7"]
13618
+ ];
13445
13619
  var SECTION_B_PROGRESSIONS = [
13446
13620
  ["F", "G", "Em7", "Am"],
13447
13621
  ["Dm7", "G7", "CM7", "A7"],
@@ -13668,10 +13842,9 @@ var HALF_BAR_FIGURES = [
13668
13842
  [EIGHTH, EIGHTH, EIGHTH, EIGHTH],
13669
13843
  // タタタタ
13670
13844
  [EIGHTH, EIGHTH, EIGHTH, EIGHTH],
13671
- [EIGHTH, EIGHTH, EIGHTH, EIGHTH],
13672
- [EIGHTH, EIGHTH, EIGHTH, EIGHTH],
13673
13845
  [QUARTER, EIGHTH, EIGHTH],
13674
13846
  // ター・タタ
13847
+ [QUARTER, EIGHTH, EIGHTH],
13675
13848
  [EIGHTH, EIGHTH, QUARTER],
13676
13849
  // タタ・ター
13677
13850
  [DOT_QUARTER, EIGHTH],
@@ -13679,8 +13852,24 @@ var HALF_BAR_FIGURES = [
13679
13852
  [DOT_QUARTER, EIGHTH],
13680
13853
  [EIGHTH, QUARTER, EIGHTH],
13681
13854
  // 頭抜き
13855
+ // 4分音符主体・ロングトーン(J-POPの歌い上げるバラード〜ミドル向け。音数過多を是正)
13856
+ [QUARTER, QUARTER],
13857
+ // ター・ター
13858
+ [QUARTER, QUARTER],
13682
13859
  [HALF],
13683
- // ロングトーン
13860
+ // ターーー
13861
+ [HALF],
13862
+ [-QUARTER, QUARTER],
13863
+ // 休・ター
13864
+ [QUARTER, -QUARTER],
13865
+ // ター・休
13866
+ // 弱起(アウフタクト)パターン:末尾から次へ飛び込む言い回し
13867
+ [-DOT_QUARTER, EIGHTH],
13868
+ // 3拍半休みからの裏拍アウフタクト
13869
+ [-QUARTER, EIGHTH, EIGHTH],
13870
+ // 2拍目裏からのアウフタクト(タタ)
13871
+ [QUARTER, -EIGHTH, EIGHTH],
13872
+ // ター・休タ(8分裏弱起)
13684
13873
  // 付点8分+16分(タッカ)。歌モノで非常に多用される跳ね・推進力の型。
13685
13874
  [DOT_EIGHTH, SIXTEENTH, EIGHTH, EIGHTH],
13686
13875
  [EIGHTH, EIGHTH, DOT_EIGHTH, SIXTEENTH],
@@ -14349,7 +14538,10 @@ var draw = (options, resolvedKey, rnd) => {
14349
14538
  };
14350
14539
  const quarterSteps = scaleStep(QUARTER);
14351
14540
  const strongStep = Math.max(1, Math.round(stepsPerBar / 2));
14352
- const sectionPlan = buildSectionPlan(options.sections ?? DEFAULT_SECTIONS);
14541
+ const sectionPlan = buildSectionPlan(
14542
+ options.sections ?? DEFAULT_SECTIONS,
14543
+ options.template
14544
+ );
14353
14545
  const totalBars = sectionPlan.reduce((sum, s) => sum + s.bars, 0);
14354
14546
  if (rnd() < 0.35) {
14355
14547
  const modType = pick(
@@ -14386,12 +14578,18 @@ var draw = (options, resolvedKey, rnd) => {
14386
14578
  (p) => p.join("|") !== progA.join("|")
14387
14579
  );
14388
14580
  const progB = pick(progBPool, rnd);
14581
+ const progC = pick(
14582
+ SECTION_C_PROGRESSIONS.filter(
14583
+ (p) => p.join("|") !== progA.join("|") && p.join("|") !== progB.join("|")
14584
+ ),
14585
+ rnd
14586
+ );
14389
14587
  const tonic = progA[0].startsWith("Am") ? "Am" : "C";
14390
14588
  const progHalf = pick(SECTION_A2_DERIVATIONS, rnd)(progA);
14391
14589
  const progFull = pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
14392
14590
  const progression = [];
14393
14591
  for (const section of sectionPlan) {
14394
- const base = section.spec.progression === "b" ? progB : progA;
14592
+ const base = section.spec.progression === "c" ? progC : section.spec.progression === "b" ? progB : progA;
14395
14593
  for (let i2 = 0; i2 < section.bars; i2 += 4) {
14396
14594
  const isLastPhrase = i2 + 4 >= section.bars;
14397
14595
  if (!isLastPhrase) {
@@ -14409,7 +14607,7 @@ var draw = (options, resolvedKey, rnd) => {
14409
14607
  const chordPattern = pick(CHORD_PATTERNS, rnd);
14410
14608
  const rootShift = resolvedKey.rootShift;
14411
14609
  const bpm = pick(BPM_CHOICES, rnd);
14412
- const sourceOf = (kind) => kind === "chorus" || kind === "interlude" ? "b" : kind === "prechorus" ? "a2" : "a";
14610
+ const sourceOf = (kind) => kind === "chorus" || kind === "interlude" || kind === "drop_chorus" ? "b" : kind === "prechorus" ? "a2" : kind === "bridge" ? "c" : "a";
14413
14611
  const units2 = [];
14414
14612
  for (const section of sectionPlan) {
14415
14613
  const unitCount = Math.max(1, Math.round(section.bars / 2));
@@ -14446,6 +14644,19 @@ var draw = (options, resolvedKey, rnd) => {
14446
14644
  const barInUnit = (bar) => bar % 2;
14447
14645
  const unitOf = (bar) => Math.floor(bar / 2);
14448
14646
  const restatementOf = (bar) => {
14647
+ const curSec = sectionAt(sectionPlan, bar);
14648
+ if (!curSec.spec.melody) return null;
14649
+ if (curSec.restatement) {
14650
+ const firstSec = sectionPlan.find(
14651
+ (s) => s.kind === curSec.kind && !s.restatement
14652
+ );
14653
+ if (firstSec) {
14654
+ const offset = bar - curSec.startBar;
14655
+ if (offset < firstSec.bars) {
14656
+ return firstSec.startBar + offset;
14657
+ }
14658
+ }
14659
+ }
14449
14660
  const u = unitOf(bar);
14450
14661
  if (units2[u].source === "silent") return null;
14451
14662
  for (let v = 0; v < u; v++) {
@@ -14475,10 +14686,9 @@ var draw = (options, resolvedKey, rnd) => {
14475
14686
  ),
14476
14687
  // 人間の曲の跳躍率は p25〜p75 で 0.40〜0.55。上限が低いとその帯へ届かない。
14477
14688
  leapAffinity: 0.08 + rnd() * 0.3,
14478
- // 4曲に1曲は変化音を使わない曲にする(調の外の音は曲の性格そのものなので、
14479
- // 全曲に掛けると「どの曲も同じ味付け」になる)。
14480
- chromaticAffinity: rnd() < 0.25 ? 0 : 0.15 + rnd() * 0.45,
14481
- barHeadWeight: rnd() < 0.65 ? 3 : 2,
14689
+ // 界隈曲らしさ:調の外の音(クロマチック)や微小な逸脱を積極的に許容する。
14690
+ chromaticAffinity: rnd() < 0.1 ? 0 : 0.3 + rnd() * 0.6,
14691
+ barHeadWeight: rnd() < 0.5 ? 3 : 2,
14482
14692
  bassStyle: pick(
14483
14693
  [
14484
14694
  "quarter",
@@ -14504,7 +14714,7 @@ var draw = (options, resolvedKey, rnd) => {
14504
14714
  subInterval: pick([3, 4, 8, 9], rnd)
14505
14715
  };
14506
14716
  const motifPool = groovyCells(MOTIF_CELLS, style.groove, rnd);
14507
- const targetNotesPerBar = 5.5 + rnd() * 2.5;
14717
+ const targetNotesPerBar = 4 + rnd() * 4;
14508
14718
  const targetRestRatio = 0.02 + rnd() * 0.1;
14509
14719
  const cellNotes = (c) => c.value.filter((v) => v > 0).length;
14510
14720
  const cellRest = (c) => {
@@ -14516,27 +14726,49 @@ var draw = (options, resolvedKey, rnd) => {
14516
14726
  }
14517
14727
  return total === 0 ? 0 : rest / total;
14518
14728
  };
14519
- const cellDistance = (c) => Math.abs(cellNotes(c) - targetNotesPerBar) + Math.abs(cellRest(c) - targetRestRatio) * 8;
14520
- const pickCell = (pool) => {
14729
+ const cellDistance = (c, densityMul = 1) => Math.abs(cellNotes(c) - targetNotesPerBar * densityMul) + Math.abs(cellRest(c) - targetRestRatio / Math.max(0.5, densityMul)) * 8;
14730
+ const pickCell = (pool, densityMul = 1) => {
14521
14731
  let best = pick(pool, rnd);
14522
14732
  for (let i2 = 0; i2 < 2; i2++) {
14523
14733
  const c = pick(pool, rnd);
14524
- if (cellDistance(c) < cellDistance(best)) best = c;
14734
+ if (cellDistance(c, densityMul) < cellDistance(best, densityMul))
14735
+ best = c;
14525
14736
  }
14526
14737
  return best;
14527
14738
  };
14528
- const motifCell = pickCell(motifPool);
14529
- const motifCell2 = rnd() < 0.45 ? motifCell : pickCell(motifPool.filter((c) => c !== motifCell));
14739
+ const motifCell = pickCell(motifPool, 0.85);
14740
+ const motifCell2 = rnd() < 0.45 ? motifCell : pickCell(
14741
+ motifPool.filter((c) => c !== motifCell),
14742
+ 0.85
14743
+ );
14530
14744
  const motifB = pickCell(
14531
- motifPool.filter((c) => c !== motifCell && c !== motifCell2)
14745
+ motifPool.filter((c) => c !== motifCell && c !== motifCell2),
14746
+ 1.2
14747
+ );
14748
+ const motifB2 = pickCell(
14749
+ motifPool.filter((c) => c !== motifB),
14750
+ 1.2
14532
14751
  );
14533
- const motifB2 = pickCell(motifPool.filter((c) => c !== motifB));
14534
14752
  const motifA2 = pickCell(
14535
14753
  motifPool.filter(
14536
14754
  (c) => c !== motifCell && c !== motifCell2 && c !== motifB
14537
- )
14755
+ ),
14756
+ 1.1
14757
+ );
14758
+ const motifA22 = rnd() < 0.45 ? motifA2 : pickCell(
14759
+ motifPool.filter((c) => c !== motifA2),
14760
+ 1.1
14761
+ );
14762
+ const motifC = pickCell(
14763
+ motifPool.filter(
14764
+ (c) => c !== motifCell && c !== motifCell2 && c !== motifB && c !== motifA2
14765
+ ),
14766
+ 0.9
14767
+ );
14768
+ const motifC2 = rnd() < 0.45 ? motifC : pickCell(
14769
+ motifPool.filter((c) => c !== motifC),
14770
+ 0.9
14538
14771
  );
14539
- const motifA22 = rnd() < 0.45 ? motifA2 : pickCell(motifPool.filter((c) => c !== motifA2));
14540
14772
  const motifVar = pickCell(
14541
14773
  motifPool.filter((c) => c !== motifCell && c !== motifCell2)
14542
14774
  );
@@ -14589,12 +14821,13 @@ var draw = (options, resolvedKey, rnd) => {
14589
14821
  }
14590
14822
  const isB = units2[u].source === "b";
14591
14823
  const isA2 = units2[u].source === "a2";
14824
+ const isC = units2[u].source === "c";
14592
14825
  const isPrechorusEnd = units2[u].section.kind === "prechorus" && bar === units2[u].section.startBar + units2[u].section.bars - 1;
14593
14826
  if (isPrechorusEnd) {
14594
14827
  barRhythms.push(scaleCell(buildUpCell.value));
14595
14828
  continue;
14596
14829
  }
14597
- const pair = () => isB ? [motifB, motifB2] : isA2 ? [motifA2, motifA22] : [motifCell, motifCell2];
14830
+ const pair = () => isB ? [motifB, motifB2] : isA2 ? [motifA2, motifA22] : isC ? [motifC, motifC2] : [motifCell, motifCell2];
14598
14831
  let cell;
14599
14832
  if (units2[u].source === "answer") {
14600
14833
  const isPeriodEnd = units2[u].landing !== null;
@@ -14625,6 +14858,8 @@ var draw = (options, resolvedKey, rnd) => {
14625
14858
  const melody = [];
14626
14859
  const submelody = [];
14627
14860
  const bass = [];
14861
+ const harmony = [];
14862
+ const pad = [];
14628
14863
  const melodyDurations = [];
14629
14864
  const barTension = new Array(totalBars).fill(0);
14630
14865
  let restSteps = 0;
@@ -14671,13 +14906,22 @@ var draw = (options, resolvedKey, rnd) => {
14671
14906
  (bar + style.arcPhase) / style.arcPeriod * Math.PI * 2
14672
14907
  );
14673
14908
  const arcCenter = MELODY_CENTER + arc * style.arcAmp;
14674
- const headTarget = prevSemi + (arcCenter - prevSemi) * 0.5;
14675
- const headSemi = nearestChordTone(
14676
- headTarget,
14677
- tones,
14678
- headWeight,
14679
- isSectionB
14680
- ).semi;
14909
+ const inUnit = barInUnit(bar);
14910
+ let headSemi;
14911
+ if (inUnit === 1 && bar > 0) {
14912
+ headSemi = nearestChordTone(prevSemi, tones, headWeight, isSectionB).semi;
14913
+ if (Math.abs(headSemi - prevSemi) > 4) {
14914
+ headSemi = nearestChordTone(prevSemi, tones, 1, isSectionB).semi;
14915
+ }
14916
+ } else {
14917
+ const headTarget = prevSemi + (arcCenter - prevSemi) * 0.5;
14918
+ headSemi = nearestChordTone(
14919
+ headTarget,
14920
+ tones,
14921
+ headWeight,
14922
+ isSectionB
14923
+ ).semi;
14924
+ }
14681
14925
  const contourOffset = barInUnit(bar) === 0 ? 0 : barRhythms[bar - 1].filter((v) => v > 0).length;
14682
14926
  const prevRole = bar > 0 ? barRoles[bar - 1] : null;
14683
14927
  const repeatShift = role === "motif" && (prevRole === "motif" || prevRole === "sequence" || prevRole === "climax") ? pick([-2, -1, 1, 2], rnd) : 0;
@@ -14857,6 +15101,48 @@ var draw = (options, resolvedKey, rnd) => {
14857
15101
  } else {
14858
15102
  pushSub(scaleStep(QUARTER), scaleStep(DOT_HALF), subSemi);
14859
15103
  }
15104
+ const barSec = sectionAt(sectionPlan, bar);
15105
+ if (!silent && (barSec.kind === "chorus" || barSec.kind === "drop_chorus")) {
15106
+ for (let i2 = 0; i2 < slots.length; i2++) {
15107
+ const hSemi = pitches[i2] + 3;
15108
+ const hTone = nearestChordTone(hSemi, tones, 2);
15109
+ const hClamped = clampSemi(hTone.semi, MELODY_LOW, MELODY_HIGH);
15110
+ const k = barKeyShift[bar];
15111
+ const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15112
+ harmony.push({
15113
+ startStep: barStart + slots[i2].at,
15114
+ pitchUnits: spelledToUnits(
15115
+ hClamped + k,
15116
+ hTone.fifth + fifthShift,
15117
+ edo
15118
+ ),
15119
+ durationSteps: slots[i2].value,
15120
+ velocity: slots[i2].at === 0 ? 82 : 76
15121
+ });
15122
+ }
15123
+ }
15124
+ if (barSec.kind === "prechorus" || barSec.kind === "chorus" || barSec.kind === "bridge" || barSec.kind === "drop_chorus") {
15125
+ const padTone = nearestChordTone(
15126
+ MELODY_HIGH + 2,
15127
+ // メロディの上の音域
15128
+ tones,
15129
+ 2
15130
+ );
15131
+ const padSemi = clampSemi(padTone.semi, MELODY_HIGH - 4, MELODY_HIGH + 8);
15132
+ const k = barKeyShift[bar];
15133
+ const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15134
+ pad.push({
15135
+ startStep: barStart,
15136
+ pitchUnits: spelledToUnits(
15137
+ padSemi + k,
15138
+ padTone.fifth + fifthShift,
15139
+ edo
15140
+ ),
15141
+ durationSteps: stepsPerBar,
15142
+ // 小節全体を伸ばす
15143
+ velocity: barSec.kind === "drop_chorus" ? 50 : 62
15144
+ });
15145
+ }
14860
15146
  const rootTone = tones[0];
14861
15147
  const fifthTone = tones.find((t) => t.weight === 3 && t !== rootTone);
14862
15148
  const thirdTone = tones.find((t) => t.weight === 2);
@@ -14963,6 +15249,69 @@ var draw = (options, resolvedKey, rnd) => {
14963
15249
  }
14964
15250
  for (const n of barBass) bass.push(n);
14965
15251
  }
15252
+ const eighthSteps = scaleStep(EIGHTH);
15253
+ for (let i2 = 0; i2 < melody.length - 1; i2++) {
15254
+ const cur = melody[i2];
15255
+ const nxt = melody[i2 + 1];
15256
+ const curEnd = cur.startStep + cur.durationSteps;
15257
+ const atBarBoundary = curEnd % stepsPerBar === 0 && nxt.startStep === curEnd;
15258
+ if (!atBarBoundary) continue;
15259
+ if (cur.durationSteps > eighthSteps) continue;
15260
+ const barIdx = Math.floor(curEnd / stepsPerBar);
15261
+ if (barIdx >= totalBars) continue;
15262
+ if (i2 + 1 >= melody.length - 1) continue;
15263
+ const curSec = sectionAt(sectionPlan, barIdx - 1);
15264
+ const nextSec = sectionAt(sectionPlan, barIdx);
15265
+ if (!nextSec.spec.melody || curSec !== nextSec) continue;
15266
+ const curSemi = Math.round(cur.pitchUnits / UNITS_PER_SEMITONE);
15267
+ const nxtSemi = Math.round(nxt.pitchUnits / UNITS_PER_SEMITONE);
15268
+ if (Math.abs(nxtSemi - curSemi) > 3) continue;
15269
+ const nextRole = barRoles[barIdx];
15270
+ let tieProb = 0.4;
15271
+ if (nextRole === "motif") tieProb = 0.05;
15272
+ else if (nextRole === "sequence") tieProb = 0.75;
15273
+ else if (nextRole === "climax") tieProb = 0.9;
15274
+ else if (nextRole === "cadence") tieProb = 0.1;
15275
+ if (rnd() < tieProb) {
15276
+ cur.durationSteps += nxt.durationSteps;
15277
+ melody.splice(i2 + 1, 1);
15278
+ if (i2 < melodyDurations.length - 1) {
15279
+ melodyDurations[i2] += melodyDurations[i2 + 1];
15280
+ melodyDurations.splice(i2 + 1, 1);
15281
+ }
15282
+ const hCurIdx = harmony.findIndex((h) => h.startStep === cur.startStep);
15283
+ const hNxtIdx = harmony.findIndex((h) => h.startStep === nxt.startStep);
15284
+ if (hCurIdx >= 0 && hNxtIdx >= 0) {
15285
+ harmony[hCurIdx].durationSteps += harmony[hNxtIdx].durationSteps;
15286
+ harmony.splice(hNxtIdx, 1);
15287
+ }
15288
+ i2--;
15289
+ }
15290
+ }
15291
+ for (let i2 = 0; i2 < melody.length; i2++) {
15292
+ const barIdx = Math.floor(melody[i2].startStep / stepsPerBar);
15293
+ if (barIdx >= totalBars) continue;
15294
+ const role = barRoles[barIdx];
15295
+ let splitProb = 0.15;
15296
+ if (role === "motif" || role === "sequence") splitProb = 0;
15297
+ else if (role === "climax") splitProb = 0.6;
15298
+ else if (role === "cadence") splitProb = 0;
15299
+ if (rnd() < splitProb && melody[i2].durationSteps === quarterSteps) {
15300
+ const half = scaleStep(EIGHTH);
15301
+ const newNote = {
15302
+ ...melody[i2],
15303
+ startStep: melody[i2].startStep + half,
15304
+ durationSteps: half
15305
+ };
15306
+ melody[i2].durationSteps = half;
15307
+ melody.splice(i2 + 1, 0, newNote);
15308
+ if (i2 < melodyDurations.length) {
15309
+ melodyDurations[i2] = half;
15310
+ melodyDurations.splice(i2 + 1, 0, half);
15311
+ }
15312
+ i2++;
15313
+ }
15314
+ }
14966
15315
  const range = (notes) => {
14967
15316
  if (notes.length === 0) return 0;
14968
15317
  const us = notes.map((n) => n.pitchUnits);
@@ -14970,7 +15319,7 @@ var draw = (options, resolvedKey, rnd) => {
14970
15319
  };
14971
15320
  const shiftUnits = semitonesToUnits(rootShift, edo);
14972
15321
  if (shiftUnits !== 0)
14973
- for (const list of [melody, submelody, bass])
15322
+ for (const list of [melody, submelody, bass, harmony, pad])
14974
15323
  for (const n of list) n.pitchUnits = n.pitchUnits + shiftUnits;
14975
15324
  return {
14976
15325
  chordProgression,
@@ -14985,6 +15334,8 @@ var draw = (options, resolvedKey, rnd) => {
14985
15334
  melody,
14986
15335
  submelody,
14987
15336
  bass,
15337
+ harmony,
15338
+ pad,
14988
15339
  melodyDurations,
14989
15340
  restSteps,
14990
15341
  totalSteps: Math.max(1, sungBars) * stepsPerBar,
@@ -15020,7 +15371,16 @@ var evaluate = (d, recent) => {
15020
15371
  fromMelody(toMetricNotes(d.submelody)),
15021
15372
  opts
15022
15373
  );
15023
- const tension = tensionFeatures(d.barTension);
15374
+ const verseBars = [];
15375
+ const chorusBars = [];
15376
+ for (const s of d.sections) {
15377
+ for (let b = s.startBar; b < s.startBar + s.bars; b++) {
15378
+ if (s.kind === "verse") verseBars.push(b);
15379
+ else if (s.kind === "chorus" || s.kind === "drop_chorus")
15380
+ chorusBars.push(b);
15381
+ }
15382
+ }
15383
+ const tension = tensionFeatures(d.barTension, { verseBars, chorusBars });
15024
15384
  const density = densityFeatures(melodyFrom, opts);
15025
15385
  const fingerprint = featureVector({
15026
15386
  entropy,
@@ -15036,6 +15396,7 @@ var evaluate = (d, recent) => {
15036
15396
  );
15037
15397
  const at = (b, v) => band(v, b[0], b[1], b[2], b[3]);
15038
15398
  const atc = (key, v) => centeredBand(v, CORPUS_BANDS[key], CORPUS_MEDIANS[key]);
15399
+ const peakBand = d.bars > 24 ? [0, 1, Math.round(d.bars / 16), Math.round(d.bars / 8) + 2] : HAND_BANDS.climaxPeaks;
15039
15400
  const scoreBreakdown = {
15040
15401
  entropy: atc("entropy", entropy),
15041
15402
  valueKinds: atc("valueKinds", valueKinds),
@@ -15053,7 +15414,7 @@ var evaluate = (d, recent) => {
15053
15414
  sim8: atc("sim8", structure.sim8),
15054
15415
  phraseBreath: atc("phraseBreath", structure.phraseBreath),
15055
15416
  climaxPosition: atc("climaxPosition", structure.climaxPosition),
15056
- climaxPeaks: at(HAND_BANDS.climaxPeaks, structure.climaxPeaks),
15417
+ climaxPeaks: at(peakBand, structure.climaxPeaks),
15057
15418
  complementarity: at(HAND_BANDS.complementarity, structure.complementarity),
15058
15419
  subDensity: at(HAND_BANDS.subDensity, d.submelody.length / d.bars),
15059
15420
  tensionRise: tension.rise,
@@ -15107,9 +15468,10 @@ var composeSong = (options) => {
15107
15468
  bestScore = stats.score;
15108
15469
  bestIsValid = ok;
15109
15470
  best = {
15110
- // ドラムは勝った候補にだけ後から付ける(メロディに依存しないので
15471
+ // ドラム・楽器は勝った候補にだけ後から付ける(メロディに依存しないので
15111
15472
  // 候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
15112
15473
  drum: "",
15474
+ instrument: "",
15113
15475
  chordProgression: d.chordProgression,
15114
15476
  chordPattern: d.chordPattern,
15115
15477
  rootShift: d.rootShift,
@@ -15122,6 +15484,8 @@ var composeSong = (options) => {
15122
15484
  melody: d.melody,
15123
15485
  submelody: d.submelody,
15124
15486
  bass: d.bass,
15487
+ harmony: d.harmony,
15488
+ pad: d.pad,
15125
15489
  stats: { ...stats, attempts: attempt, rejected }
15126
15490
  };
15127
15491
  }
@@ -15129,6 +15493,7 @@ var composeSong = (options) => {
15129
15493
  result.stats.attempts = count;
15130
15494
  result.stats.rejected = rejected;
15131
15495
  result.drum = pickBuiltinDrum(result, rnd);
15496
+ result.instrument = pickBuiltinInstrument(result, rnd);
15132
15497
  return result;
15133
15498
  };
15134
15499
  var pickBuiltinDrum = (song, rnd) => {
@@ -15137,6 +15502,35 @@ var pickBuiltinDrum = (song, rnd) => {
15137
15502
  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"];
15138
15503
  return pick(pool, rnd);
15139
15504
  };
15505
+ var pickBuiltinInstrument = (song, rnd) => {
15506
+ const eighth = BASE_STEPS_PER_BAR / 8;
15507
+ const short = song.melody.filter((n) => n.durationSteps <= eighth).length / Math.max(1, song.melody.length);
15508
+ if (rnd() < 0.06) {
15509
+ return pick(["japanese_wa", "arabic_exotic"], rnd);
15510
+ }
15511
+ let pool;
15512
+ if (song.drum === "dance" || song.drum === "disco") {
15513
+ pool = song.bpm >= 150 ? ["cyber_punk", "synth_pop", "retro_game"] : ["synth_pop", "retro_game", "rock", "piano"];
15514
+ } else if (song.drum === "bossa") {
15515
+ pool = ["jazz_night", "acoustic", "piano"];
15516
+ } else if (song.bpm <= 105 && short < 0.4) {
15517
+ pool = ["ambient_cloud", "acoustic", "orchestra", "piano", "fantasy_rpg"];
15518
+ } else if (song.bpm >= 145) {
15519
+ pool = ["rock", "synth_pop", "cyber_punk", "retro_game", "piano"];
15520
+ } else if (song.stats.leapRatio >= 0.45) {
15521
+ pool = ["fantasy_rpg", "orchestra", "acoustic", "piano"];
15522
+ } else {
15523
+ pool = [
15524
+ "piano",
15525
+ "acoustic",
15526
+ "synth_pop",
15527
+ "rock",
15528
+ "jazz_night",
15529
+ "fantasy_rpg"
15530
+ ];
15531
+ }
15532
+ return pick(pool, rnd);
15533
+ };
15140
15534
  var LYRIC_WORDS = [
15141
15535
  "\u3042\u3055",
15142
15536
  "\u3072\u304B\u308A",
@@ -15538,74 +15932,89 @@ var buildUI = (target, options) => {
15538
15932
  <details class="dtm-panel" data-dtm-acc="macro">
15539
15933
  <summary>\u30DE\u30AF\u30ED</summary>
15540
15934
  <div class="dtm-panel-body">
15541
- <div class="dtm-row ${showCompose ? "" : "dtm-hidden"}" data-dtm="compose-row">
15542
- <button class="dtm-btn dtm-btn--success" data-dtm="macro-compose" title="\u30B3\u30FC\u30C9\u9032\u884C\u30FB\u30E1\u30ED\u30C7\u30A3\u30FB\u30B5\u30D6\u30E1\u30ED\u30FB\u30D9\u30FC\u30B9\u30FB\u4F34\u594F\u30FB\u30C9\u30E9\u30E0\u3092\u81EA\u52D5\u3067\u4F5C\u308A\u307E\u3059">\u4F5C\u66F2</button>
15543
- <button class="dtm-btn dtm-btn--success" data-dtm="macro-compose-vocal" title="\u4F5C\u66F2\u3057\u305F\u3046\u3048\u3067\u3001\u30E1\u30ED\u30C7\u30A3\u306B\u6B4C\u8A5E\u3092\u4ED8\u3051\u3066\u6B4C\u308F\u305B\u307E\u3059">\u6B4C\u5165\u308A\u4F5C\u66F2</button>
15544
- <button class="dtm-infobtn" data-dtm="macro-compose-info" title="\u4F5C\u66F2\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15545
- <span class="dtm-grow"></span>
15546
- </div>
15547
- <div class="dtm-row ${showCompose ? "" : "dtm-hidden"}" data-dtm="compose-sections-row">
15548
- <span class="dtm-label">\u4F5C\u308B\u90E8\u5206</span>
15549
- <div class="dtm-checks" data-dtm="compose-sections">
15550
- <label class="dtm-check"><input type="checkbox" value="intro" checked>\u30A4\u30F3\u30C8\u30ED</label>
15551
- <label class="dtm-check"><input type="checkbox" value="verse" checked>A\u30E1\u30ED</label>
15552
- <label class="dtm-check"><input type="checkbox" value="prechorus" checked>B\u30E1\u30ED</label>
15553
- <label class="dtm-check"><input type="checkbox" value="chorus" checked>\u30B5\u30D3</label>
15554
- <label class="dtm-check"><input type="checkbox" value="interlude">\u9593\u594F</label>
15555
- <label class="dtm-check"><input type="checkbox" value="outro">\u30A2\u30A6\u30C8\u30ED</label>
15935
+ <div class="dtm-compose-container ${showCompose ? "" : "dtm-hidden"}" data-dtm="compose-container">
15936
+ <div class="dtm-row" data-dtm="compose-row">
15937
+ <button class="dtm-btn dtm-btn--success" data-dtm="macro-compose" title="\u30B3\u30FC\u30C9\u9032\u884C\u30FB\u30E1\u30ED\u30C7\u30A3\u30FB\u30B5\u30D6\u30E1\u30ED\u30FB\u30D9\u30FC\u30B9\u30FB\u4F34\u594F\u30FB\u30C9\u30E9\u30E0\u3092\u81EA\u52D5\u3067\u4F5C\u308A\u307E\u3059">\u4F5C\u66F2</button>
15938
+ <button class="dtm-btn dtm-btn--success" data-dtm="macro-compose-vocal" title="\u4F5C\u66F2\u3057\u305F\u3046\u3048\u3067\u3001\u30E1\u30ED\u30C7\u30A3\u306B\u6B4C\u8A5E\u3092\u4ED8\u3051\u3066\u6B4C\u308F\u305B\u307E\u3059">\u6B4C\u5165\u308A\u4F5C\u66F2</button>
15939
+ <button class="dtm-infobtn" data-dtm="macro-compose-info" title="\u4F5C\u66F2\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15940
+ <span class="dtm-grow"></span>
15941
+ </div>
15942
+ <div class="dtm-row" data-dtm="compose-template-row">
15943
+ <span class="dtm-label">\u69CB\u6210</span>
15944
+ <select class="dtm-select" data-dtm="compose-template" title="J-POP\u738B\u9053\u306A\u3069\u306E\u30D7\u30EA\u30BB\u30C3\u30C8\u69CB\u6210\u3092\u9078\u3073\u307E\u3059">
15945
+ <option value="custom">\u81EA\u7531\u9078\u629E\uFF08\u4E0B\u8A18\u30C1\u30A7\u30C3\u30AF\uFF09</option>
15946
+ <option value="1chorus">1\u30B3\u30FC\u30E9\u30B9\uFF08\u77ED\u3081\u30FB\u521D\u5FC3\u8005\u5411\u3051\uFF09</option>
15947
+ <option value="jpop_standard">JPOP\u738B\u9053\uFF08\u30DE\u30EA\u30FC\u30B4\u30FC\u30EB\u30C9\u578B 2\u756A/C\u30E1\u30ED/\u30E9\u30B9\u30B5\u30D3\uFF09</option>
15948
+ <option value="jpop_drop">\u843D\u3061\u30B5\u30D3\u5165\u308A\uFF08JPOP\u738B\u9053 + \u30E9\u30B9\u30B5\u30D3\u524D\u843D\u3061\u30B5\u30D3\uFF09</option>
15949
+ <option value="vocaloid">\u30DC\u30AB\u30ED\u738B\u9053\uFF08\u75BE\u8D70\u30FB2\u756A/C\u30E1\u30ED/\u30E9\u30B9\u30B5\u30D3\uFF09</option>
15950
+ <option value="verse_chorus">Verse-Chorus\uFF08B\u30E1\u30ED\u306A\u3057\u30FB\u6D0B\u697D\u98A8\uFF09</option>
15951
+ </select>
15952
+ </div>
15953
+ <div class="dtm-row" data-dtm="compose-sections-row">
15954
+ <span class="dtm-label">\u4F5C\u308B\u90E8\u5206</span>
15955
+ <div class="dtm-checks" data-dtm="compose-sections">
15956
+ <label class="dtm-check"><input type="checkbox" value="intro" checked>\u30A4\u30F3\u30C8\u30ED</label>
15957
+ <label class="dtm-check"><input type="checkbox" value="verse" checked>A\u30E1\u30ED</label>
15958
+ <label class="dtm-check"><input type="checkbox" value="prechorus" checked>B\u30E1\u30ED</label>
15959
+ <label class="dtm-check"><input type="checkbox" value="chorus" checked>\u30B5\u30D3</label>
15960
+ <label class="dtm-check"><input type="checkbox" value="bridge">C\u30E1\u30ED</label>
15961
+ <label class="dtm-check"><input type="checkbox" value="drop_chorus">\u843D\u3061\u30B5\u30D3</label>
15962
+ <label class="dtm-check"><input type="checkbox" value="interlude">\u9593\u594F</label>
15963
+ <label class="dtm-check"><input type="checkbox" value="outro">\u30A2\u30A6\u30C8\u30ED</label>
15964
+ </div>
15965
+ <span class="dtm-grow"></span>
15966
+ <span class="dtm-hint" data-dtm="compose-sections-len"></span>
15967
+ </div>
15968
+ <div class="dtm-row" data-dtm="compose-key-row">
15969
+ <span class="dtm-label">\u30D9\u30FC\u30B9\u8ABF</span>
15970
+ <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">
15971
+ <option value="any" title="\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057</option>
15972
+ <optgroup label="\u57FA\u672C">
15973
+ <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>
15974
+ <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>
15975
+ </optgroup>
15976
+ <optgroup label="\u96F0\u56F2\u6C17\u304B\u3089\u9078\u3076\uFF08\u62BD\u9078\uFF09">
15977
+ <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>
15978
+ <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>
15979
+ <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>
15980
+ <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>
15981
+ <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>
15982
+ <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>
15983
+ <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>
15984
+ <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>
15985
+ </optgroup>
15986
+ <optgroup label="\u9577\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
15987
+ <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>
15988
+ <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>
15989
+ <option value="key_D" title="\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0">\u30CB\u9577\u8ABF (D)</option>
15990
+ <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>
15991
+ <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>
15992
+ <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>
15993
+ <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>
15994
+ <option value="key_G" title="\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F">\u30C8\u9577\u8ABF (G)</option>
15995
+ <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>
15996
+ <option value="key_A" title="\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D">\u30A4\u9577\u8ABF (A)</option>
15997
+ <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>
15998
+ <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>
15999
+ </optgroup>
16000
+ <optgroup label="\u77ED\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
16001
+ <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>
16002
+ <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>
16003
+ <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>
16004
+ <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>
16005
+ <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>
16006
+ <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>
16007
+ <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>
16008
+ <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>
16009
+ <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>
16010
+ <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>
16011
+ <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>
16012
+ <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>
16013
+ </optgroup>
16014
+ </select>
16015
+ <span class="dtm-grow"></span>
16016
+ <span class="dtm-hint" data-dtm="compose-key-hint"></span>
15556
16017
  </div>
15557
- <span class="dtm-grow"></span>
15558
- <span class="dtm-hint" data-dtm="compose-sections-len"></span>
15559
- </div>
15560
- <div class="dtm-row ${showCompose ? "" : "dtm-hidden"}" data-dtm="compose-key-row">
15561
- <span class="dtm-label">\u30D9\u30FC\u30B9\u8ABF</span>
15562
- <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">
15563
- <option value="any" title="\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057</option>
15564
- <optgroup label="\u57FA\u672C">
15565
- <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>
15566
- <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>
15567
- </optgroup>
15568
- <optgroup label="\u96F0\u56F2\u6C17\u304B\u3089\u9078\u3076\uFF08\u62BD\u9078\uFF09">
15569
- <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>
15570
- <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>
15571
- <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>
15572
- <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>
15573
- <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>
15574
- <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>
15575
- <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>
15576
- <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>
15577
- </optgroup>
15578
- <optgroup label="\u9577\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
15579
- <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>
15580
- <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>
15581
- <option value="key_D" title="\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0">\u30CB\u9577\u8ABF (D)</option>
15582
- <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>
15583
- <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>
15584
- <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>
15585
- <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>
15586
- <option value="key_G" title="\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F">\u30C8\u9577\u8ABF (G)</option>
15587
- <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>
15588
- <option value="key_A" title="\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D">\u30A4\u9577\u8ABF (A)</option>
15589
- <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>
15590
- <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>
15591
- </optgroup>
15592
- <optgroup label="\u77ED\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
15593
- <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>
15594
- <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>
15595
- <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>
15596
- <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>
15597
- <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>
15598
- <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>
15599
- <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>
15600
- <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>
15601
- <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>
15602
- <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>
15603
- <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>
15604
- <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>
15605
- </optgroup>
15606
- </select>
15607
- <span class="dtm-grow"></span>
15608
- <span class="dtm-hint" data-dtm="compose-key-hint"></span>
15609
16018
  </div>
15610
16019
  <div class="dtm-row">
15611
16020
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
@@ -15803,6 +16212,7 @@ var buildUI = (target, options) => {
15803
16212
  transposeApplyBtn: sel("transpose-apply"),
15804
16213
  transposeInfoBtn: sel("transpose-info"),
15805
16214
  macroCompose: sel("macro-compose"),
16215
+ composeTemplate: sel("compose-template"),
15806
16216
  composeSections: sel("compose-sections"),
15807
16217
  composeSectionsLen: sel("compose-sections-len"),
15808
16218
  composeKey: sel("compose-key"),
@@ -15945,6 +16355,45 @@ var INSTRUMENT_PRESETS = {
15945
16355
  }
15946
16356
  };
15947
16357
 
16358
+ // src/macro-state.ts
16359
+ var MACRO_STORAGE_KEYS = {
16360
+ template: "dtm-macro:template",
16361
+ sections: "dtm-macro:sections",
16362
+ key: "dtm-macro:key",
16363
+ shift: "dtm-macro:shift",
16364
+ transpose: "dtm-macro:transpose"
16365
+ };
16366
+ var readMacroSetting = (key) => {
16367
+ try {
16368
+ if (typeof localStorage === "undefined" || !localStorage) return null;
16369
+ return localStorage.getItem(MACRO_STORAGE_KEYS[key]);
16370
+ } catch (_) {
16371
+ return null;
16372
+ }
16373
+ };
16374
+ var writeMacroSetting = (key, value) => {
16375
+ try {
16376
+ if (typeof localStorage === "undefined" || !localStorage) return;
16377
+ localStorage.setItem(MACRO_STORAGE_KEYS[key], value);
16378
+ } catch (_) {
16379
+ }
16380
+ };
16381
+ var readMacroSections = () => {
16382
+ const raw = readMacroSetting("sections");
16383
+ if (!raw) return null;
16384
+ try {
16385
+ const parsed = JSON.parse(raw);
16386
+ if (Array.isArray(parsed) && parsed.every((x2) => typeof x2 === "string")) {
16387
+ return parsed;
16388
+ }
16389
+ } catch (_) {
16390
+ }
16391
+ return null;
16392
+ };
16393
+ var writeMacroSections = (sections) => {
16394
+ writeMacroSetting("sections", JSON.stringify(sections));
16395
+ };
16396
+
15948
16397
  // src/macros.ts
15949
16398
  var SCALES = [
15950
16399
  [0, 2, 4, 5, 7, 9, 11],
@@ -18288,12 +18737,15 @@ var pickComposeVocal = (exclude) => {
18288
18737
  var ADVANCED_COMPOSE_LAYOUT = [
18289
18738
  { index: 0, part: "melody", octave: 0, volume: 104 },
18290
18739
  { index: 1, part: "melody", octave: 1, volume: 62 },
18291
- { index: 2, part: "submelody", octave: 0, volume: 88 },
18292
- { index: 3, part: "bass", octave: 0, volume: 92 },
18293
- { index: 4, part: "bass", octave: -1, volume: 58 },
18294
- { index: 5, part: "block", octave: 0, volume: 62 },
18295
- { index: 6, part: "arpeggio", octave: 0, volume: 54 },
18296
- { index: 7, part: "offbeat", octave: 0, volume: 50 }
18740
+ { index: 2, part: "harmony", octave: 0, volume: 82 },
18741
+ { index: 3, part: "submelody", octave: 0, volume: 86 },
18742
+ { index: 4, part: "bass", octave: 0, volume: 92 },
18743
+ { index: 5, part: "bass", octave: -1, volume: 58 },
18744
+ { index: 6, part: "pad", octave: 0, volume: 64 },
18745
+ { index: 7, part: "block", octave: 0, volume: 62 },
18746
+ { index: 8, part: "arpeggio", octave: 0, volume: 54 },
18747
+ { index: 9, part: "offbeat", octave: 0, volume: 50 },
18748
+ { index: 10, part: "uwamono", octave: 1, volume: 56 }
18297
18749
  ];
18298
18750
  var LYRIC_MODEL_CATEGORIES = [
18299
18751
  {
@@ -18583,6 +19035,7 @@ var mountDAW = (target, options = {}) => {
18583
19035
  options.onDrumFontChange?.(font);
18584
19036
  };
18585
19037
  let currentInstrument = "";
19038
+ let autoComposeInstrument = null;
18586
19039
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
18587
19040
  let trackFxAdvancedOpen = readPanelOpen("track-fx-advanced") ?? false;
18588
19041
  let lyricAdvancedOpen = readPanelOpen("lyric-advanced") ?? false;
@@ -19620,8 +20073,8 @@ var mountDAW = (target, options = {}) => {
19620
20073
  redrawAll();
19621
20074
  },
19622
20075
  onEnd: (interrupted) => {
19623
- options.onScheduleFade?.(null);
19624
20076
  if (interrupted) {
20077
+ options.onScheduleFade?.(null);
19625
20078
  playbackState = "paused";
19626
20079
  pausedPlayStep = currentPlayStep;
19627
20080
  } else {
@@ -21753,6 +22206,10 @@ var mountDAW = (target, options = {}) => {
21753
22206
  }
21754
22207
  return ADVANCED_COMPOSE_LAYOUT.filter((l) => l.part === "bass").map((l) => trackStates[l.index]).filter((t) => !!t);
21755
22208
  };
22209
+ const selectedComposeTemplate = () => {
22210
+ const val = refs.composeTemplate?.value;
22211
+ return val && val !== "custom" ? val : void 0;
22212
+ };
21756
22213
  const selectedComposeSections = () => {
21757
22214
  const boxes = [
21758
22215
  ...refs.composeSections.querySelectorAll(
@@ -21763,11 +22220,97 @@ var mountDAW = (target, options = {}) => {
21763
22220
  return picked.length > 0 ? picked : DEFAULT_SECTIONS;
21764
22221
  };
21765
22222
  const updateComposeSectionsLen = () => {
21766
- const plan = buildSectionPlan(selectedComposeSections());
22223
+ const tmpl = selectedComposeTemplate();
22224
+ const plan = buildSectionPlan(selectedComposeSections(), tmpl);
21767
22225
  const bars = plan.reduce((sum, x2) => sum + x2.bars, 0);
21768
22226
  refs.composeSectionsLen.textContent = `${bars}\u5C0F\u7BC0`;
21769
22227
  };
21770
- refs.composeSections.addEventListener("change", updateComposeSectionsLen);
22228
+ const savedTemplate = readMacroSetting("template");
22229
+ if (savedTemplate && refs.composeTemplate) {
22230
+ const hasOption = Array.from(refs.composeTemplate.options).some(
22231
+ (opt) => opt.value === savedTemplate
22232
+ );
22233
+ if (hasOption) {
22234
+ refs.composeTemplate.value = savedTemplate;
22235
+ }
22236
+ }
22237
+ const savedSections = readMacroSections();
22238
+ if (savedSections && refs.composeSections) {
22239
+ const boxes = refs.composeSections.querySelectorAll(
22240
+ 'input[type="checkbox"]'
22241
+ );
22242
+ const validValues = new Set(savedSections);
22243
+ for (const box of boxes) {
22244
+ box.checked = validValues.has(box.value);
22245
+ }
22246
+ } else if (savedTemplate && savedTemplate !== "custom" && refs.composeSections) {
22247
+ const tmpl = STRUCTURE_TEMPLATES.find((t) => t.name === savedTemplate);
22248
+ if (tmpl) {
22249
+ const planSet = new Set(tmpl.plan);
22250
+ const boxes = refs.composeSections.querySelectorAll(
22251
+ 'input[type="checkbox"]'
22252
+ );
22253
+ for (const box of boxes) {
22254
+ box.checked = planSet.has(box.value);
22255
+ }
22256
+ }
22257
+ }
22258
+ const savedKey = readMacroSetting("key");
22259
+ if (savedKey && refs.composeKey) {
22260
+ const hasOption = Array.from(refs.composeKey.options).some(
22261
+ (opt) => opt.value === savedKey
22262
+ );
22263
+ if (hasOption) {
22264
+ refs.composeKey.value = savedKey;
22265
+ }
22266
+ }
22267
+ const savedShift = readMacroSetting("shift");
22268
+ if (savedShift && refs.shiftSelect) {
22269
+ const hasOption = Array.from(refs.shiftSelect.options).some(
22270
+ (opt) => opt.value === savedShift
22271
+ );
22272
+ if (hasOption) {
22273
+ refs.shiftSelect.value = savedShift;
22274
+ }
22275
+ }
22276
+ const savedTranspose = readMacroSetting("transpose");
22277
+ if (savedTranspose && refs.transposeSelect) {
22278
+ const hasOption = Array.from(refs.transposeSelect.options).some(
22279
+ (opt) => opt.value === savedTranspose
22280
+ );
22281
+ if (hasOption) {
22282
+ refs.transposeSelect.value = savedTranspose;
22283
+ }
22284
+ }
22285
+ refs.composeSections.addEventListener("change", () => {
22286
+ if (refs.composeTemplate) {
22287
+ refs.composeTemplate.value = "custom";
22288
+ writeMacroSetting("template", "custom");
22289
+ }
22290
+ writeMacroSections(selectedComposeSections());
22291
+ updateComposeSectionsLen();
22292
+ });
22293
+ const composeTemplate = refs.composeTemplate;
22294
+ if (composeTemplate) {
22295
+ composeTemplate.addEventListener("change", () => {
22296
+ writeMacroSetting("template", composeTemplate.value);
22297
+ const tmplName = selectedComposeTemplate();
22298
+ if (tmplName) {
22299
+ const tmpl = STRUCTURE_TEMPLATES.find((t) => t.name === tmplName);
22300
+ if (tmpl) {
22301
+ const planSet = new Set(tmpl.plan);
22302
+ const boxes = refs.composeSections.querySelectorAll(
22303
+ 'input[type="checkbox"]'
22304
+ );
22305
+ for (const box of boxes) {
22306
+ box.checked = planSet.has(box.value);
22307
+ }
22308
+ }
22309
+ }
22310
+ writeMacroSections(selectedComposeSections());
22311
+ updateComposeSectionsLen();
22312
+ });
22313
+ }
21771
22314
  updateComposeSectionsLen();
21772
22315
  const updateComposeKeyHint = () => {
21773
22316
  if (!refs.composeKey || !refs.composeKeyHint) return;
@@ -21775,17 +22318,33 @@ var mountDAW = (target, options = {}) => {
21775
22318
  refs.composeKeyHint.textContent = desc;
21776
22319
  refs.composeKeyHint.title = desc;
21777
22320
  };
21778
- if (refs.composeKey) {
21779
- refs.composeKey.addEventListener("change", updateComposeKeyHint);
22321
+ const composeKey = refs.composeKey;
22322
+ if (composeKey) {
22323
+ composeKey.addEventListener("change", () => {
22324
+ writeMacroSetting("key", composeKey.value);
22325
+ updateComposeKeyHint();
22326
+ });
21780
22327
  updateComposeKeyHint();
21781
22328
  }
22329
+ if (refs.shiftSelect) {
22330
+ refs.shiftSelect.addEventListener("change", () => {
22331
+ writeMacroSetting("shift", refs.shiftSelect.value);
22332
+ });
22333
+ }
22334
+ if (refs.transposeSelect) {
22335
+ refs.transposeSelect.addEventListener("change", () => {
22336
+ writeMacroSetting("transpose", refs.transposeSelect.value);
22337
+ });
22338
+ }
21782
22339
  const runCompose = (withVocal) => {
21783
22340
  stop();
21784
22341
  overlayDuring(() => {
22342
+ const tmpl = selectedComposeTemplate();
21785
22343
  const song = composeSong({
21786
22344
  stepsPerBar: renderConfig.stepsPerBar,
21787
22345
  edo: renderConfig.edo,
21788
22346
  sections: selectedComposeSections(),
22347
+ template: tmpl,
21789
22348
  baseKey: refs.composeKey?.value ?? "any",
21790
22349
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
21791
22350
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
@@ -21824,7 +22383,19 @@ var mountDAW = (target, options = {}) => {
21824
22383
  for (const layer of ADVANCED_COMPOSE_LAYOUT) {
21825
22384
  const track = trackStates[layer.index];
21826
22385
  if (!track) continue;
21827
- const notes = layer.part === "melody" ? song.melody : layer.part === "submelody" ? song.submelody : layer.part === "bass" ? song.bass : buildChordPlacements({
22386
+ const notes = layer.part === "melody" ? song.melody : layer.part === "harmony" ? song.harmony : layer.part === "submelody" ? song.submelody : layer.part === "bass" ? song.bass : layer.part === "pad" ? song.pad : layer.part === "uwamono" ? buildChordPlacements({
22387
+ edo: renderConfig.edo,
22388
+ chordStr: song.chordProgression,
22389
+ patternType: "arpeggio",
22390
+ rootShift: song.rootShift,
22391
+ bpm: song.bpm,
22392
+ stepsPerBar: renderConfig.stepsPerBar
22393
+ }).map((p) => ({
22394
+ startStep: p.startStep,
22395
+ pitchUnits: p.pitchUnits,
22396
+ durationSteps: p.durationSteps,
22397
+ velocity: Math.max(30, p.velocity - 14)
22398
+ })) : buildChordPlacements({
21828
22399
  edo: renderConfig.edo,
21829
22400
  chordStr: song.chordProgression,
21830
22401
  patternType: layer.part,
@@ -21863,6 +22434,12 @@ var mountDAW = (target, options = {}) => {
21863
22434
  refs.drumSelect.value = song.drum;
21864
22435
  options.onDrumChange?.(song.drum);
21865
22436
  applyDrumPatternFont(song.drum);
22437
+ const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
22438
+ if (shouldAutoInstrument && song.instrument) {
22439
+ currentInstrument = song.instrument;
22440
+ autoComposeInstrument = song.instrument;
22441
+ options.onInstrumentChange?.(song.instrument);
22442
+ }
21866
22443
  for (const t of bassTracksForCompose()) {
21867
22444
  t.trackCompression = AUTO_ROLE_MIX.bass.compression;
21868
22445
  options.onTrackCompressionChange?.(t.config.id, t.trackCompression);
@@ -24135,6 +24712,8 @@ var createDtmStudio = async (options = {}) => {
24135
24712
  if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
24136
24713
  fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
24137
24714
  fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
24715
+ fadeGain.gain.setValueAtTime(0, fadeOutEndAt + 2);
24716
+ fadeGain.gain.setValueAtTime(1, fadeOutEndAt + 2);
24138
24717
  }
24139
24718
  };
24140
24719
  const clipMeter = createClipMeter(audioCtx, glueMakeup);
@@ -24362,13 +24941,17 @@ var createDtmStudio = async (options = {}) => {
24362
24941
  }
24363
24942
  const select = doc.createElement("select");
24364
24943
  select.className = "dtm-select dtm-grow";
24944
+ const autoOpt = doc.createElement("option");
24945
+ autoOpt.value = "auto";
24946
+ autoOpt.textContent = "\u2728 \u81EA\u52D5\uFF08\u66F2\u8ABF\u9023\u52D5\uFF09";
24947
+ select.appendChild(autoOpt);
24365
24948
  for (const [key, p] of Object.entries(INSTRUMENT_PRESETS)) {
24366
24949
  const o = doc.createElement("option");
24367
24950
  o.value = key;
24368
24951
  o.textContent = p.displayName;
24369
24952
  select.appendChild(o);
24370
24953
  }
24371
- select.value = opts.value && INSTRUMENT_PRESETS[opts.value] ? opts.value : defaultPreset;
24954
+ select.value = opts.value === "auto" || opts.value && INSTRUMENT_PRESETS[opts.value] ? opts.value : defaultPreset;
24372
24955
  wrapper.appendChild(select);
24373
24956
  let busy = false;
24374
24957
  const onChange = async () => {
@@ -24377,6 +24960,10 @@ var createDtmStudio = async (options = {}) => {
24377
24960
  busy = true;
24378
24961
  const key = select.value;
24379
24962
  opts.onChange?.(key);
24963
+ if (key === "auto") {
24964
+ busy = false;
24965
+ return;
24966
+ }
24380
24967
  const trackIds = opts.getTrackIds?.() ?? [...TRACK_ROLES];
24381
24968
  const isAdvanced = trackIds.includes("t0");
24382
24969
  const mode = isAdvanced ? "advanced" : "simple";
@@ -24394,7 +24981,7 @@ var createDtmStudio = async (options = {}) => {
24394
24981
  element: wrapper,
24395
24982
  select,
24396
24983
  setValue: (k) => {
24397
- if (INSTRUMENT_PRESETS[k]) select.value = k;
24984
+ if (k === "auto" || INSTRUMENT_PRESETS[k]) select.value = k;
24398
24985
  },
24399
24986
  getValue: () => select.value,
24400
24987
  destroy: () => {
@@ -24406,8 +24993,15 @@ var createDtmStudio = async (options = {}) => {
24406
24993
  await listReady;
24407
24994
  nameToKey = await buildNameToKeyMapping();
24408
24995
  await Promise.all([drumReady, loadPreset(defaultPreset)]);
24996
+ const restoreFadeGainIfMuted = () => {
24997
+ if (fadeGain.gain.value < 1) {
24998
+ fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
24999
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
25000
+ }
25001
+ };
24409
25002
  const playDrum = (e) => {
24410
25003
  if (!sfDrum.font) return;
25004
+ if (e.when === 0) restoreFadeGainIfMuted();
24411
25005
  if (KICK_PITCHES.has(e.pitch)) duckBassForKick(e.when);
24412
25006
  sfDrum.play({
24413
25007
  ctx: audioCtx,
@@ -24451,6 +25045,7 @@ var createDtmStudio = async (options = {}) => {
24451
25045
  }
24452
25046
  }
24453
25047
  const playNote4 = (e) => {
25048
+ if (e.when === 0) restoreFadeGainIfMuted();
24454
25049
  const { trackIdx, role } = resolveTrackIdxAndRole(e.trackId);
24455
25050
  const overrideKey = trackIdx >= 0 ? trackInstOverrides.get(trackIdx) : void 0;
24456
25051
  let sfInst;
@@ -24476,12 +25071,33 @@ var createDtmStudio = async (options = {}) => {
24476
25071
  duration: e.duration
24477
25072
  });
24478
25073
  };
25074
+ let daw;
24479
25075
  let presetSelect = null;
24480
25076
  const handleInstrumentChange = (key) => {
25077
+ if (key === "auto") {
25078
+ if (presetSelect) {
25079
+ presetSelect.setValue("auto");
25080
+ }
25081
+ onInstrumentChange?.("auto");
25082
+ return;
25083
+ }
24481
25084
  editorPreset = key;
24482
25085
  if (presetSelect) {
24483
25086
  presetSelect.setValue(key);
24484
25087
  }
25088
+ if (INSTRUMENT_PRESETS[key]) {
25089
+ const wasPlaying = daw?.getPlaybackState() === "playing";
25090
+ if (wasPlaying) daw.pause();
25091
+ daw?.setLoading?.(true);
25092
+ void loadPreset(
25093
+ key,
25094
+ trackIds,
25095
+ isAdvancedMode ? "advanced" : "simple"
25096
+ ).finally(() => {
25097
+ daw?.setLoading?.(false);
25098
+ if (wasPlaying) daw?.play();
25099
+ });
25100
+ }
24485
25101
  onInstrumentChange?.(key);
24486
25102
  };
24487
25103
  const handleTrackInstrumentChange = async (trackIndex, instrumentName) => {
@@ -24495,7 +25111,6 @@ var createDtmStudio = async (options = {}) => {
24495
25111
  trackInstOverrides.set(trackIndex, key);
24496
25112
  await loadInstrument(key);
24497
25113
  };
24498
- let daw;
24499
25114
  const base = {
24500
25115
  getAudioTime: () => audioCtx.currentTime,
24501
25116
  onResumeAudio: async () => {
@@ -25253,6 +25868,7 @@ export {
25253
25868
  KOE_VOICEBANK_LABELS,
25254
25869
  KOE_VOICEBANK_TERMS,
25255
25870
  LinkedList,
25871
+ MACRO_STORAGE_KEYS,
25256
25872
  MAJOR_KEY_IDS,
25257
25873
  MAX_VOCAL_VOLUME,
25258
25874
  MICRO_STEP,
@@ -25355,6 +25971,8 @@ export {
25355
25971
  playNote,
25356
25972
  playPlacements,
25357
25973
  playSingingMML,
25974
+ readMacroSections,
25975
+ readMacroSetting,
25358
25976
  resolveComposeKey,
25359
25977
  resolveDrumPattern,
25360
25978
  resolveLoopPoint,
@@ -25377,5 +25995,7 @@ export {
25377
25995
  unitsToMidi,
25378
25996
  unitsToMidiDetune,
25379
25997
  unitsToPitchV1,
25380
- vocalVolumeToGain
25998
+ vocalVolumeToGain,
25999
+ writeMacroSections,
26000
+ writeMacroSetting
25381
26001
  };