@onjmin/dtm 2.1.4 → 2.1.6

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],
13859
+ [HALF],
13860
+ // ターーー
13682
13861
  [HALF],
13683
- // ロングトーン
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],
@@ -17980,7 +18429,7 @@ var COMPOSE_INFO_HTML = `
17980
18429
  <tr><td>\u30A2\u30A6\u30C8\u30ED</td><td>\u6709\u308A</td><td>\u4F4E\u3081</td><td>\u8584\u3044</td><td>\u4E3B\u97F3\u3078\u7740\u5730</td></tr>
17981
18430
  </table>
17982
18431
  <p>\u30A4\u30F3\u30C8\u30ED\u3068\u9593\u594F\u306F\u30E1\u30ED\u30C7\u30A3\u3092\u66F8\u304D\u307E\u305B\u3093\uFF08\u4F34\u594F\u30FB\u30D9\u30FC\u30B9\u30FB\u30C9\u30E9\u30E0\u30FB\u30B5\u30D6\u30E1\u30ED\u3060\u3051\u304C\u9CF4\u308A\u307E\u3059\uFF09\u3002\u3053\u3053\u306B\u6B4C\u30E1\u30ED\u3092\u7F6E\u304F\u3068\u3001\u3069\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u3082\u540C\u3058\u9854\u306B\u306A\u3063\u3066\u300C\u305A\u3063\u3068\u6B4C\u3063\u3066\u3044\u308B\u66F2\u300D\u306B\u306A\u3063\u3066\u3057\u307E\u3046\u305F\u3081\u3067\u3059\u3002\u30BB\u30AF\u30B7\u30E7\u30F3\u306E\u982D\u3067\u306F\u30C9\u30E9\u30E0\u304C\u30AF\u30E9\u30C3\u30B7\u30E5\u3092\u53E9\u304D\u3001\u7D42\u308F\u308A\u306E1\u3064\u624B\u524D\u3067\u30D5\u30A3\u30EB\u304C\u5165\u308A\u307E\u3059\u3002</p>
17983
- <p>\u30CE\u30FC\u30C8\u3092\u914D\u7F6E\u3059\u308B\u3060\u3051\u3067\u3001\u697D\u5668\u30FB\u97F3\u91CF\u30FB\u30DF\u30C3\u30AF\u30B9\u306F\u5909\u3048\u307E\u305B\u3093\u3002\u97F3\u306E\u30D0\u30E9\u30F3\u30B9\u3092\u6574\u3048\u305F\u3044\u3068\u304D\u306F\u300C\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A\u300D\u306E<strong>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0</strong>\u3092\u7D9A\u3051\u3066\u62BC\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30B7\u30F3\u30D7\u30EB\u30E2\u30FC\u30C9\u306F\u30C8\u30E9\u30C3\u30AF\u305D\u306E\u3082\u306E\u304C\u5F79\u5272\uFF08\u30E1\u30ED\u30C7\u30A3\uFF0F\u30B5\u30D6\u30E1\u30ED\uFF0F\u30D9\u30FC\u30B9\uFF0F\u4F34\u594F\uFF09\u306A\u306E\u3067\u3001\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u306F\u305D\u306E\u5F79\u5272\u3069\u304A\u308A\u306B\u697D\u5668\u3068\u97F3\u91CF\u3092\u5F53\u3066\u307E\u3059\u3002</p>
18432
+ <p>\u30CE\u30FC\u30C8\u306E\u914D\u7F6E\u306B\u52A0\u3048\u3066\u3001\u30C6\u30F3\u30DD\u30FB\u30C9\u30E9\u30E0\u30FB\u697D\u5668\u30FB<strong>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0</strong>\uFF08\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u91CF\u30D0\u30E9\u30F3\u30B9\u30FB\u5B9A\u4F4D\u30FBEQ\u30FB\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u30FB\u30EA\u30D0\u30FC\u30D6\u30FB\u30DE\u30B9\u30BF\u8A2D\u5B9A\uFF09\u3082\u81EA\u52D5\u3067\u4E00\u62EC\u8A2D\u5B9A\u3055\u308C\u307E\u3059\u3002\u3067\u304D\u305F\u5F8C\u306B\u5FAE\u8ABF\u6574\u3057\u305F\u3044\u3068\u304D\u306F\u3001\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u8A2D\u5B9A\u3084\u300C\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A\u300D\u304B\u3089\u81EA\u7531\u306B\u5909\u66F4\u3067\u304D\u307E\u3059\u3002</p>
17984
18433
  <h4>\u66F2\u306E\u7D44\u307F\u7ACB\u3066\u65B9</h4>
17985
18434
  <p>\u5358\u7D14\u306B\u30E9\u30F3\u30C0\u30E0\u306A\u97F3\u3092\u4E26\u3079\u3066\u3044\u308B\u308F\u3051\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u6B21\u306E\u9806\u756A\u3067\u7D44\u307F\u7ACB\u3066\u3066\u3044\u307E\u3059\u3002</p>
17986
18435
  <ol>
@@ -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 {
@@ -21576,7 +22029,7 @@ var mountDAW = (target, options = {}) => {
21576
22029
  refs.autoMasterInfoBtn.addEventListener("click", () => {
21577
22030
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
21578
22031
  });
21579
- refs.autoMasterBtn.addEventListener("click", () => {
22032
+ const applyAutoMastering = () => {
21580
22033
  if (observedPlayMs >= PEAK_SAMPLE_MIN_PLAY_MS && observedPeakMax > 0) {
21581
22034
  const targetPeak = 0.5;
21582
22035
  const scale = targetPeak / observedPeakMax;
@@ -21729,6 +22182,9 @@ var mountDAW = (target, options = {}) => {
21729
22182
  }
21730
22183
  }
21731
22184
  updateTrackPanel();
22185
+ };
22186
+ refs.autoMasterBtn.addEventListener("click", () => {
22187
+ applyAutoMastering();
21732
22188
  });
21733
22189
  refs.drumSelect.addEventListener("change", () => {
21734
22190
  currentDrumPattern = refs.drumSelect.value;
@@ -21746,12 +22202,9 @@ var mountDAW = (target, options = {}) => {
21746
22202
  refs.macroComposeInfo.addEventListener("click", () => {
21747
22203
  showModal("\u4F5C\u66F2\u306E\u89E3\u8AAC", COMPOSE_INFO_HTML);
21748
22204
  });
21749
- const bassTracksForCompose = () => {
21750
- if (!isAdvanced) {
21751
- const t = trackStates.find((x2) => x2.config.id === "bass");
21752
- return t ? [t] : [];
21753
- }
21754
- return ADVANCED_COMPOSE_LAYOUT.filter((l) => l.part === "bass").map((l) => trackStates[l.index]).filter((t) => !!t);
22205
+ const selectedComposeTemplate = () => {
22206
+ const val = refs.composeTemplate?.value;
22207
+ return val && val !== "custom" ? val : void 0;
21755
22208
  };
21756
22209
  const selectedComposeSections = () => {
21757
22210
  const boxes = [
@@ -21763,11 +22216,97 @@ var mountDAW = (target, options = {}) => {
21763
22216
  return picked.length > 0 ? picked : DEFAULT_SECTIONS;
21764
22217
  };
21765
22218
  const updateComposeSectionsLen = () => {
21766
- const plan = buildSectionPlan(selectedComposeSections());
22219
+ const tmpl = selectedComposeTemplate();
22220
+ const plan = buildSectionPlan(selectedComposeSections(), tmpl);
21767
22221
  const bars = plan.reduce((sum, x2) => sum + x2.bars, 0);
21768
22222
  refs.composeSectionsLen.textContent = `${bars}\u5C0F\u7BC0`;
21769
22223
  };
21770
- refs.composeSections.addEventListener("change", updateComposeSectionsLen);
22224
+ const savedTemplate = readMacroSetting("template");
22225
+ if (savedTemplate && refs.composeTemplate) {
22226
+ const hasOption = Array.from(refs.composeTemplate.options).some(
22227
+ (opt) => opt.value === savedTemplate
22228
+ );
22229
+ if (hasOption) {
22230
+ refs.composeTemplate.value = savedTemplate;
22231
+ }
22232
+ }
22233
+ const savedSections = readMacroSections();
22234
+ if (savedSections && refs.composeSections) {
22235
+ const boxes = refs.composeSections.querySelectorAll(
22236
+ 'input[type="checkbox"]'
22237
+ );
22238
+ const validValues = new Set(savedSections);
22239
+ for (const box of boxes) {
22240
+ box.checked = validValues.has(box.value);
22241
+ }
22242
+ } else if (savedTemplate && savedTemplate !== "custom" && refs.composeSections) {
22243
+ const tmpl = STRUCTURE_TEMPLATES.find((t) => t.name === savedTemplate);
22244
+ if (tmpl) {
22245
+ const planSet = new Set(tmpl.plan);
22246
+ const boxes = refs.composeSections.querySelectorAll(
22247
+ 'input[type="checkbox"]'
22248
+ );
22249
+ for (const box of boxes) {
22250
+ box.checked = planSet.has(box.value);
22251
+ }
22252
+ }
22253
+ }
22254
+ const savedKey = readMacroSetting("key");
22255
+ if (savedKey && refs.composeKey) {
22256
+ const hasOption = Array.from(refs.composeKey.options).some(
22257
+ (opt) => opt.value === savedKey
22258
+ );
22259
+ if (hasOption) {
22260
+ refs.composeKey.value = savedKey;
22261
+ }
22262
+ }
22263
+ const savedShift = readMacroSetting("shift");
22264
+ if (savedShift && refs.shiftSelect) {
22265
+ const hasOption = Array.from(refs.shiftSelect.options).some(
22266
+ (opt) => opt.value === savedShift
22267
+ );
22268
+ if (hasOption) {
22269
+ refs.shiftSelect.value = savedShift;
22270
+ }
22271
+ }
22272
+ const savedTranspose = readMacroSetting("transpose");
22273
+ if (savedTranspose && refs.transposeSelect) {
22274
+ const hasOption = Array.from(refs.transposeSelect.options).some(
22275
+ (opt) => opt.value === savedTranspose
22276
+ );
22277
+ if (hasOption) {
22278
+ refs.transposeSelect.value = savedTranspose;
22279
+ }
22280
+ }
22281
+ refs.composeSections.addEventListener("change", () => {
22282
+ if (refs.composeTemplate) {
22283
+ refs.composeTemplate.value = "custom";
22284
+ writeMacroSetting("template", "custom");
22285
+ }
22286
+ writeMacroSections(selectedComposeSections());
22287
+ updateComposeSectionsLen();
22288
+ });
22289
+ const composeTemplate = refs.composeTemplate;
22290
+ if (composeTemplate) {
22291
+ composeTemplate.addEventListener("change", () => {
22292
+ writeMacroSetting("template", composeTemplate.value);
22293
+ const tmplName = selectedComposeTemplate();
22294
+ if (tmplName) {
22295
+ const tmpl = STRUCTURE_TEMPLATES.find((t) => t.name === tmplName);
22296
+ if (tmpl) {
22297
+ const planSet = new Set(tmpl.plan);
22298
+ const boxes = refs.composeSections.querySelectorAll(
22299
+ 'input[type="checkbox"]'
22300
+ );
22301
+ for (const box of boxes) {
22302
+ box.checked = planSet.has(box.value);
22303
+ }
22304
+ }
22305
+ }
22306
+ writeMacroSections(selectedComposeSections());
22307
+ updateComposeSectionsLen();
22308
+ });
22309
+ }
21771
22310
  updateComposeSectionsLen();
21772
22311
  const updateComposeKeyHint = () => {
21773
22312
  if (!refs.composeKey || !refs.composeKeyHint) return;
@@ -21775,17 +22314,33 @@ var mountDAW = (target, options = {}) => {
21775
22314
  refs.composeKeyHint.textContent = desc;
21776
22315
  refs.composeKeyHint.title = desc;
21777
22316
  };
21778
- if (refs.composeKey) {
21779
- refs.composeKey.addEventListener("change", updateComposeKeyHint);
22317
+ const composeKey = refs.composeKey;
22318
+ if (composeKey) {
22319
+ composeKey.addEventListener("change", () => {
22320
+ writeMacroSetting("key", composeKey.value);
22321
+ updateComposeKeyHint();
22322
+ });
21780
22323
  updateComposeKeyHint();
21781
22324
  }
22325
+ if (refs.shiftSelect) {
22326
+ refs.shiftSelect.addEventListener("change", () => {
22327
+ writeMacroSetting("shift", refs.shiftSelect.value);
22328
+ });
22329
+ }
22330
+ if (refs.transposeSelect) {
22331
+ refs.transposeSelect.addEventListener("change", () => {
22332
+ writeMacroSetting("transpose", refs.transposeSelect.value);
22333
+ });
22334
+ }
21782
22335
  const runCompose = (withVocal) => {
21783
22336
  stop();
21784
22337
  overlayDuring(() => {
22338
+ const tmpl = selectedComposeTemplate();
21785
22339
  const song = composeSong({
21786
22340
  stepsPerBar: renderConfig.stepsPerBar,
21787
22341
  edo: renderConfig.edo,
21788
22342
  sections: selectedComposeSections(),
22343
+ template: tmpl,
21789
22344
  baseKey: refs.composeKey?.value ?? "any",
21790
22345
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
21791
22346
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
@@ -21824,7 +22379,19 @@ var mountDAW = (target, options = {}) => {
21824
22379
  for (const layer of ADVANCED_COMPOSE_LAYOUT) {
21825
22380
  const track = trackStates[layer.index];
21826
22381
  if (!track) continue;
21827
- const notes = layer.part === "melody" ? song.melody : layer.part === "submelody" ? song.submelody : layer.part === "bass" ? song.bass : buildChordPlacements({
22382
+ 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({
22383
+ edo: renderConfig.edo,
22384
+ chordStr: song.chordProgression,
22385
+ patternType: "arpeggio",
22386
+ rootShift: song.rootShift,
22387
+ bpm: song.bpm,
22388
+ stepsPerBar: renderConfig.stepsPerBar
22389
+ }).map((p) => ({
22390
+ startStep: p.startStep,
22391
+ pitchUnits: p.pitchUnits,
22392
+ durationSteps: p.durationSteps,
22393
+ velocity: Math.max(30, p.velocity - 14)
22394
+ })) : buildChordPlacements({
21828
22395
  edo: renderConfig.edo,
21829
22396
  chordStr: song.chordProgression,
21830
22397
  patternType: layer.part,
@@ -21863,9 +22430,11 @@ var mountDAW = (target, options = {}) => {
21863
22430
  refs.drumSelect.value = song.drum;
21864
22431
  options.onDrumChange?.(song.drum);
21865
22432
  applyDrumPatternFont(song.drum);
21866
- for (const t of bassTracksForCompose()) {
21867
- t.trackCompression = AUTO_ROLE_MIX.bass.compression;
21868
- options.onTrackCompressionChange?.(t.config.id, t.trackCompression);
22433
+ const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
22434
+ if (shouldAutoInstrument && song.instrument) {
22435
+ currentInstrument = song.instrument;
22436
+ autoComposeInstrument = song.instrument;
22437
+ options.onInstrumentChange?.(song.instrument);
21869
22438
  }
21870
22439
  if (withVocal) {
21871
22440
  const melodyTrack = isAdvanced ? trackStates[0] : trackStates.find((t) => t.config.id === "melody");
@@ -21881,6 +22450,7 @@ var mountDAW = (target, options = {}) => {
21881
22450
  fireLyricsChange(melodyTrack);
21882
22451
  }
21883
22452
  }
22453
+ applyAutoMastering();
21884
22454
  if (refs.composeKeyHint) {
21885
22455
  refs.composeKeyHint.textContent = `${song.keyLabel} \u3067\u4F5C\u6210`;
21886
22456
  refs.composeKeyHint.title = `${song.keyLabel}${song.moodLabel ? `\uFF08${song.moodLabel}\uFF09` : ""}`;
@@ -23052,6 +23622,49 @@ var mountDAW = (target, options = {}) => {
23052
23622
  };
23053
23623
  };
23054
23624
 
23625
+ // src/global-state.ts
23626
+ var GLOBAL_STORAGE_KEYS = {
23627
+ edo: "dtm-global:edo",
23628
+ loop: "dtm-global:loop",
23629
+ masterVolume: "dtm-global:master-volume",
23630
+ masterComp: "dtm-global:master-comp",
23631
+ reverbAmount: "dtm-global:reverb-amount",
23632
+ reverbDecay: "dtm-global:reverb-decay",
23633
+ reverbPreDelay: "dtm-global:reverb-predelay",
23634
+ delayAmount: "dtm-global:delay-amount",
23635
+ delayDivision: "dtm-global:delay-division",
23636
+ fadeIn: "dtm-global:fade-in",
23637
+ fadeOut: "dtm-global:fade-out"
23638
+ };
23639
+ var readGlobalSetting = (key) => {
23640
+ try {
23641
+ if (typeof localStorage === "undefined" || !localStorage) return null;
23642
+ return localStorage.getItem(GLOBAL_STORAGE_KEYS[key]);
23643
+ } catch (_) {
23644
+ return null;
23645
+ }
23646
+ };
23647
+ var writeGlobalSetting = (key, value) => {
23648
+ try {
23649
+ if (typeof localStorage === "undefined" || !localStorage) return;
23650
+ localStorage.setItem(GLOBAL_STORAGE_KEYS[key], value);
23651
+ } catch (_) {
23652
+ }
23653
+ };
23654
+ var readGlobalNumber = (key, min, max) => {
23655
+ const raw = readGlobalSetting(key);
23656
+ if (raw === null) return null;
23657
+ const n = Number.parseFloat(raw);
23658
+ if (Number.isNaN(n)) return null;
23659
+ return Math.max(min, Math.min(max, n));
23660
+ };
23661
+ var readGlobalBool = (key) => {
23662
+ const raw = readGlobalSetting(key);
23663
+ if (raw === "1" || raw === "true") return true;
23664
+ if (raw === "0" || raw === "false") return false;
23665
+ return null;
23666
+ };
23667
+
23055
23668
  // src/headless-singing-player.ts
23056
23669
  var STEPS_PER_BEAT4 = 48;
23057
23670
  var STEPS_PER_BAR3 = 192;
@@ -24135,6 +24748,8 @@ var createDtmStudio = async (options = {}) => {
24135
24748
  if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
24136
24749
  fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
24137
24750
  fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
24751
+ fadeGain.gain.setValueAtTime(0, fadeOutEndAt + 2);
24752
+ fadeGain.gain.setValueAtTime(1, fadeOutEndAt + 2);
24138
24753
  }
24139
24754
  };
24140
24755
  const clipMeter = createClipMeter(audioCtx, glueMakeup);
@@ -24406,8 +25021,15 @@ var createDtmStudio = async (options = {}) => {
24406
25021
  await listReady;
24407
25022
  nameToKey = await buildNameToKeyMapping();
24408
25023
  await Promise.all([drumReady, loadPreset(defaultPreset)]);
25024
+ const restoreFadeGainIfMuted = () => {
25025
+ if (fadeGain.gain.value < 1) {
25026
+ fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
25027
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
25028
+ }
25029
+ };
24409
25030
  const playDrum = (e) => {
24410
25031
  if (!sfDrum.font) return;
25032
+ if (e.when === 0) restoreFadeGainIfMuted();
24411
25033
  if (KICK_PITCHES.has(e.pitch)) duckBassForKick(e.when);
24412
25034
  sfDrum.play({
24413
25035
  ctx: audioCtx,
@@ -24451,6 +25073,7 @@ var createDtmStudio = async (options = {}) => {
24451
25073
  }
24452
25074
  }
24453
25075
  const playNote4 = (e) => {
25076
+ if (e.when === 0) restoreFadeGainIfMuted();
24454
25077
  const { trackIdx, role } = resolveTrackIdxAndRole(e.trackId);
24455
25078
  const overrideKey = trackIdx >= 0 ? trackInstOverrides.get(trackIdx) : void 0;
24456
25079
  let sfInst;
@@ -24476,12 +25099,26 @@ var createDtmStudio = async (options = {}) => {
24476
25099
  duration: e.duration
24477
25100
  });
24478
25101
  };
25102
+ let daw;
24479
25103
  let presetSelect = null;
24480
25104
  const handleInstrumentChange = (key) => {
24481
25105
  editorPreset = key;
24482
25106
  if (presetSelect) {
24483
25107
  presetSelect.setValue(key);
24484
25108
  }
25109
+ if (INSTRUMENT_PRESETS[key]) {
25110
+ const wasPlaying = daw?.getPlaybackState() === "playing";
25111
+ if (wasPlaying) daw.pause();
25112
+ daw?.setLoading?.(true);
25113
+ void loadPreset(
25114
+ key,
25115
+ trackIds,
25116
+ isAdvancedMode ? "advanced" : "simple"
25117
+ ).finally(() => {
25118
+ daw?.setLoading?.(false);
25119
+ if (wasPlaying) daw?.play();
25120
+ });
25121
+ }
24485
25122
  onInstrumentChange?.(key);
24486
25123
  };
24487
25124
  const handleTrackInstrumentChange = async (trackIndex, instrumentName) => {
@@ -24495,7 +25132,6 @@ var createDtmStudio = async (options = {}) => {
24495
25132
  trackInstOverrides.set(trackIndex, key);
24496
25133
  await loadInstrument(key);
24497
25134
  };
24498
- let daw;
24499
25135
  const base = {
24500
25136
  getAudioTime: () => audioCtx.currentTime,
24501
25137
  onResumeAudio: async () => {
@@ -25245,6 +25881,7 @@ export {
25245
25881
  EDO31_NAMES,
25246
25882
  FADE_IN_MARK,
25247
25883
  FADE_OUT_MARK,
25884
+ GLOBAL_STORAGE_KEYS,
25248
25885
  GM_INSTRUMENT_NAMES,
25249
25886
  INSTRUMENT_PRESETS,
25250
25887
  KEY_COUNT,
@@ -25253,6 +25890,7 @@ export {
25253
25890
  KOE_VOICEBANK_LABELS,
25254
25891
  KOE_VOICEBANK_TERMS,
25255
25892
  LinkedList,
25893
+ MACRO_STORAGE_KEYS,
25256
25894
  MAJOR_KEY_IDS,
25257
25895
  MAX_VOCAL_VOLUME,
25258
25896
  MICRO_STEP,
@@ -25355,6 +25993,11 @@ export {
25355
25993
  playNote,
25356
25994
  playPlacements,
25357
25995
  playSingingMML,
25996
+ readGlobalBool,
25997
+ readGlobalNumber,
25998
+ readGlobalSetting,
25999
+ readMacroSections,
26000
+ readMacroSetting,
25358
26001
  resolveComposeKey,
25359
26002
  resolveDrumPattern,
25360
26003
  resolveLoopPoint,
@@ -25377,5 +26020,8 @@ export {
25377
26020
  unitsToMidi,
25378
26021
  unitsToMidiDetune,
25379
26022
  unitsToPitchV1,
25380
- vocalVolumeToGain
26023
+ vocalVolumeToGain,
26024
+ writeGlobalSetting,
26025
+ writeMacroSections,
26026
+ writeMacroSetting
25381
26027
  };