@onjmin/dtm 2.1.9 → 2.1.11

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
@@ -1763,11 +1763,19 @@ var createChannelStrip = (ctx, destination, options = {}) => {
1763
1763
  const duckGain = ctx.createGain();
1764
1764
  duckGain.gain.value = 1;
1765
1765
  const DUCK_ATTACK_SEC = 8e-3;
1766
+ let duckReleaseEndAt = 0;
1766
1767
  const duck = (atTime, depth = 0.3, releaseSec = 0.18) => {
1767
1768
  const t = Math.max(atTime, ctx.currentTime);
1768
1769
  const floor = clamp(1 - depth, 0, 1);
1769
- duckGain.gain.linearRampToValueAtTime(floor, t + DUCK_ATTACK_SEC);
1770
- duckGain.gain.linearRampToValueAtTime(1, t + DUCK_ATTACK_SEC + releaseSec);
1770
+ const param = duckGain.gain;
1771
+ if (t >= duckReleaseEndAt) {
1772
+ param.setValueAtTime(1, t);
1773
+ } else if (typeof param.cancelAndHoldAtTime === "function") {
1774
+ param.cancelAndHoldAtTime(t);
1775
+ }
1776
+ param.linearRampToValueAtTime(floor, t + DUCK_ATTACK_SEC);
1777
+ param.linearRampToValueAtTime(1, t + DUCK_ATTACK_SEC + releaseSec);
1778
+ duckReleaseEndAt = t + DUCK_ATTACK_SEC + releaseSec;
1771
1779
  };
1772
1780
  eqHigh.connect(compressor);
1773
1781
  compressor.connect(duckGain);
@@ -2064,12 +2072,15 @@ var buildChordPlacements = (options) => {
2064
2072
  }
2065
2073
  }
2066
2074
  } else if (patternType === "alternating") {
2075
+ const quarterSteps = Math.floor(stepsPerBar / 4);
2067
2076
  notes.forEach((noteOffset, i2) => {
2068
- const stepOffset = i2 * Math.floor(stepsPerBar / 4);
2077
+ const stepOffset = i2 * quarterSteps;
2078
+ const isLast = i2 === notes.length - 1;
2079
+ const durationSteps = isLast ? Math.max(12, noteLength - stepOffset) : Math.max(12, quarterSteps);
2069
2080
  placements.push({
2070
2081
  startStep: chord.whenStep + stepOffset,
2071
2082
  pitchUnits: toUnits(noteOffset),
2072
- durationSteps: Math.max(12, Math.floor(stepsPerBar / 4)),
2083
+ durationSteps,
2073
2084
  velocity: 100
2074
2085
  });
2075
2086
  });
@@ -4697,6 +4708,45 @@ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
4697
4708
  };
4698
4709
  var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100;
4699
4710
 
4711
+ // src/safety-limiter.ts
4712
+ var THRESHOLD_DB = -1;
4713
+ var KNEE_DB = 0;
4714
+ var RATIO = 20;
4715
+ var ATTACK_SEC = 1e-3;
4716
+ var RELEASE_SEC = 0.1;
4717
+ var CURVE_SAMPLES2 = 2048;
4718
+ var SOFT_KNEE = 0.85;
4719
+ var CEILING = 0.98;
4720
+ var SHAPER_RANGE = 4;
4721
+ var OVERSAMPLE2 = "none";
4722
+ var createSoftClipCurve = () => {
4723
+ const curve = new Float32Array(CURVE_SAMPLES2);
4724
+ const span = CEILING - SOFT_KNEE;
4725
+ for (let i2 = 0; i2 < CURVE_SAMPLES2; i2++) {
4726
+ const x2 = (i2 / (CURVE_SAMPLES2 - 1) * 2 - 1) * SHAPER_RANGE;
4727
+ const a = Math.abs(x2);
4728
+ curve[i2] = Math.sign(x2) * (a <= SOFT_KNEE ? a : SOFT_KNEE + span * Math.tanh((a - SOFT_KNEE) / span));
4729
+ }
4730
+ return curve;
4731
+ };
4732
+ var createSafetyLimiter = (ctx, destination) => {
4733
+ const limiter = ctx.createDynamicsCompressor();
4734
+ limiter.threshold.value = THRESHOLD_DB;
4735
+ limiter.knee.value = KNEE_DB;
4736
+ limiter.ratio.value = RATIO;
4737
+ limiter.attack.value = ATTACK_SEC;
4738
+ limiter.release.value = RELEASE_SEC;
4739
+ const preScale = ctx.createGain();
4740
+ preScale.gain.value = 1 / SHAPER_RANGE;
4741
+ const softClip = ctx.createWaveShaper();
4742
+ softClip.curve = createSoftClipCurve();
4743
+ softClip.oversample = OVERSAMPLE2;
4744
+ limiter.connect(preScale);
4745
+ preScale.connect(softClip);
4746
+ softClip.connect(destination);
4747
+ return limiter;
4748
+ };
4749
+
4700
4750
  // src/sequencer.ts
4701
4751
  var STEPS_PER_BEAT = 48;
4702
4752
  var PLAN_TIME = 0.5;
@@ -7160,6 +7210,9 @@ var SONG_DRUM_PATTERNS = {
7160
7210
 
7161
7211
  // src/synth.ts
7162
7212
  var freqFromPitch = (pitchUnits) => unitsToHz(pitchUnits);
7213
+ var MIN_ATTACK_SEC = 15e-4;
7214
+ var MIN_RELEASE_SEC = 4e-3;
7215
+ var TAIL_RATIO = 1e-3;
7163
7216
  var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7164
7217
  const wave = tone.wave ?? "square";
7165
7218
  const attack = tone.attack ?? 0;
@@ -7178,25 +7231,25 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7178
7231
  osc.frequency.value = freqFromPitch(e.pitchUnits);
7179
7232
  const t0 = ctx.currentTime + e.when;
7180
7233
  const peak = Math.max(1e-4, 0.06 * e.volume * 1.5 * gainScale);
7234
+ const tail = peak * TAIL_RATIO;
7235
+ const stopAt = t0 + e.duration + MIN_RELEASE_SEC;
7181
7236
  if (tone.decay) {
7182
7237
  gain.gain.setValueAtTime(1e-4, t0);
7183
7238
  gain.gain.linearRampToValueAtTime(peak, t0 + Math.max(3e-3, attack));
7184
- gain.gain.exponentialRampToValueAtTime(1e-3, t0 + e.duration);
7239
+ gain.gain.exponentialRampToValueAtTime(tail, t0 + e.duration);
7185
7240
  } else {
7186
7241
  const releaseTime = Math.min(0.02, e.duration * 0.1);
7187
7242
  const sustainDuration = e.duration - releaseTime;
7188
- if (attack > 0) {
7189
- gain.gain.setValueAtTime(1e-4, t0);
7190
- gain.gain.linearRampToValueAtTime(
7191
- peak,
7192
- t0 + Math.min(attack, sustainDuration)
7193
- );
7194
- } else {
7195
- gain.gain.setValueAtTime(peak, t0);
7196
- }
7243
+ const attackTime = Math.min(
7244
+ Math.max(attack, MIN_ATTACK_SEC),
7245
+ sustainDuration
7246
+ );
7247
+ gain.gain.setValueAtTime(1e-4, t0);
7248
+ gain.gain.linearRampToValueAtTime(peak, t0 + attackTime);
7197
7249
  gain.gain.setValueAtTime(peak, t0 + sustainDuration);
7198
- gain.gain.exponentialRampToValueAtTime(1e-3, t0 + e.duration);
7250
+ gain.gain.exponentialRampToValueAtTime(tail, t0 + e.duration);
7199
7251
  }
7252
+ gain.gain.linearRampToValueAtTime(0, stopAt);
7200
7253
  osc.connect(gain);
7201
7254
  let panner = null;
7202
7255
  if (typeof ctx.createStereoPanner === "function" && e.pan) {
@@ -7208,7 +7261,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7208
7261
  gain.connect(compressor);
7209
7262
  }
7210
7263
  osc.start(t0);
7211
- osc.stop(t0 + e.duration + 0.02);
7264
+ osc.stop(stopAt);
7212
7265
  osc.onended = () => {
7213
7266
  osc.disconnect();
7214
7267
  gain.disconnect();
@@ -7223,14 +7276,21 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7223
7276
  if (isKick) {
7224
7277
  const osc = ctx.createOscillator();
7225
7278
  const g2 = ctx.createGain();
7279
+ const kickPeak = vol * 0.135;
7280
+ const kickDecayEnd = t0 + 0.18;
7281
+ const kickEnd = kickDecayEnd + MIN_RELEASE_SEC;
7226
7282
  osc.frequency.setValueAtTime(150, t0);
7227
7283
  osc.frequency.exponentialRampToValueAtTime(50, t0 + 0.12);
7228
- g2.gain.setValueAtTime(vol * 0.135, t0);
7229
- g2.gain.exponentialRampToValueAtTime(1e-3, t0 + 0.18);
7284
+ g2.gain.setValueAtTime(kickPeak, t0);
7285
+ g2.gain.exponentialRampToValueAtTime(kickPeak * TAIL_RATIO, kickDecayEnd);
7286
+ g2.gain.linearRampToValueAtTime(0, kickEnd);
7230
7287
  osc.connect(g2).connect(compressor);
7231
7288
  osc.start(t0);
7232
- osc.stop(t0 + 0.2);
7233
- osc.onended = () => osc.disconnect();
7289
+ osc.stop(kickEnd);
7290
+ osc.onended = () => {
7291
+ osc.disconnect();
7292
+ g2.disconnect();
7293
+ };
7234
7294
  return;
7235
7295
  }
7236
7296
  const dur = isSnareLike ? 0.18 : 0.05;
@@ -7244,8 +7304,13 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7244
7304
  filter.type = isSnareLike ? "bandpass" : "highpass";
7245
7305
  filter.frequency.value = isSnareLike ? 2e3 : 8e3;
7246
7306
  const g = ctx.createGain();
7247
- g.gain.setValueAtTime(vol * (isSnareLike ? 0.105 : 0.06), t0);
7248
- g.gain.exponentialRampToValueAtTime(1e-3, t0 + dur);
7307
+ const noisePeak = vol * (isSnareLike ? 0.105 : 0.06);
7308
+ g.gain.setValueAtTime(noisePeak, t0);
7309
+ g.gain.exponentialRampToValueAtTime(
7310
+ noisePeak * TAIL_RATIO,
7311
+ t0 + dur - MIN_RELEASE_SEC
7312
+ );
7313
+ g.gain.linearRampToValueAtTime(0, t0 + dur);
7249
7314
  src.connect(filter).connect(g).connect(compressor);
7250
7315
  src.start(t0);
7251
7316
  src.stop(t0 + dur);
@@ -7307,7 +7372,7 @@ var playPlacements = (placements, options) => {
7307
7372
  reverbPreDelay.connect(reverbConvolver);
7308
7373
  reverbConvolver.connect(reverbWetGain);
7309
7374
  reverbWetGain.connect(finalMix);
7310
- finalMix.connect(rawDestination);
7375
+ finalMix.connect(createSafetyLimiter(ctx, rawDestination));
7311
7376
  const channelStrips = /* @__PURE__ */ new Map();
7312
7377
  const getChannelStrip = (index) => {
7313
7378
  let strip = channelStrips.get(index);
@@ -8121,6 +8186,22 @@ var DAW_CSS = `
8121
8186
  opacity: 0.85;
8122
8187
  }
8123
8188
  .dtm-pill:not(.dtm-pill--active):active { transform: translate(1px,1px); box-shadow: none; }
8189
+ /* \u30DC\u30FC\u30AB\u30EB\u304C\u9078\u629E\u3055\u308C\u3066\u3044\u308B\u30C8\u30E9\u30C3\u30AF\u306E\u30BF\u30D6\u306B\u3001\u3046\u3063\u3059\u3089\u58F0\u306E\u30A2\u30A4\u30B3\u30F3\u3092\u91CD\u306D\u308B\uFF08\u80CC\u666F\u8272\u30FB\u30B5\u30A4\u30BA\u306F\u5909\u3048\u306A\u3044\uFF09 */
8190
+ .dtm-pill--vocal::after {
8191
+ content: '';
8192
+ position: absolute;
8193
+ inset: 0;
8194
+ background-image: var(--dtm-pill-icon);
8195
+ background-size: contain;
8196
+ background-repeat: no-repeat;
8197
+ background-position: right center;
8198
+ opacity: 0.4;
8199
+ pointer-events: none;
8200
+ }
8201
+ .dtm-pill__label {
8202
+ position: relative;
8203
+ z-index: 1;
8204
+ }
8124
8205
  /* \u518D\u751F\u4E2D\u3001\u5B9F\u969B\u306B\u767A\u97F3\u3057\u305F\u77AC\u9593\u3060\u3051\u70B9\u706F\uFF08\u3069\u306E\u30BF\u30D6\u304C\u4ECA\u9CF4\u3063\u3066\u3044\u308B\u304B\u8996\u899A\u7684\u306B\u5206\u304B\u308B\u3088\u3046\u306B\uFF09 */
8125
8206
  .dtm-pill--sounding {
8126
8207
  filter: brightness(1.6);
@@ -9746,17 +9827,24 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9746
9827
  if (!audioCtx) audioCtx = new AudioContext();
9747
9828
  return audioCtx;
9748
9829
  };
9830
+ let limiterNode = null;
9831
+ const ensureOutput = () => {
9832
+ const ctx = ensureCtx();
9833
+ if (!limiterNode) limiterNode = createSafetyLimiter(ctx, ctx.destination);
9834
+ return limiterNode;
9835
+ };
9749
9836
  let synthInstance = null;
9750
9837
  const ensureSynth = () => {
9751
- if (!synthInstance) synthInstance = createSynth(ensureCtx());
9838
+ if (!synthInstance) {
9839
+ synthInstance = createSynth(ensureCtx(), ensureOutput());
9840
+ }
9752
9841
  return synthInstance;
9753
9842
  };
9754
9843
  let voices = null;
9755
9844
  const ensureVoices = () => {
9756
9845
  if (options.singingVoices) return options.singingVoices;
9757
9846
  if (!voices) {
9758
- const ctx = ensureCtx();
9759
- voices = createSingingVoices(ctx, ctx.destination);
9847
+ voices = createSingingVoices(ensureCtx(), ensureOutput());
9760
9848
  voices.setVolume(trackVolume / 100 * (masterVolume / 100));
9761
9849
  }
9762
9850
  return voices;
@@ -10818,6 +10906,12 @@ var SoundFont = class _SoundFont {
10818
10906
  // ファミリごとに変える。
10819
10907
  /** アタック(無音からピークまで)秒。クリック防止の最小限。全楽器共通。 */
10820
10908
  static attackSec = 5e-3;
10909
+ /**
10910
+ * 消え際に最低限確保する秒数。アタックと対になるクリック防止で、**サンプルが
10911
+ * 終わるまでに**必ずここまでに 0 へ落とし切る。振幅が残ったままバッファが尽きたり
10912
+ * `stop()` が来たりすると、その瞬間の値がそのまま段差になりプチノイズが出る。
10913
+ */
10914
+ static minReleaseSec = 4e-3;
10821
10915
  /**
10822
10916
  * 減衰の型ごとのエンベロープ。
10823
10917
  *
@@ -11004,14 +11098,23 @@ var SoundFont = class _SoundFont {
11004
11098
  const startGainTime = Math.max(ctx.currentTime, _when);
11005
11099
  g.gain.setValueAtTime(0, startGainTime);
11006
11100
  const env = _SoundFont.envelopes[this.style.env] ?? _SoundFont.envelopes.sustain;
11007
- const limit = src.loop ? Number.POSITIVE_INFINITY : _when + _param.max - startOffsetSec;
11101
+ const playRate = _param.playbackRate * 2 ** ((humanizeCents + detuneCents) / 1200);
11102
+ const sampleEnd = _when + (buffer.duration - startOffsetSec) / playRate;
11103
+ const limit = src.loop ? Number.POSITIVE_INFINITY : sampleEnd;
11008
11104
  const attackEnd = Math.min(startGainTime + _SoundFont.attackSec, limit);
11009
11105
  const decayEnd = Math.min(
11010
11106
  Math.max(attackEnd, attackEnd + env.decaySec),
11011
11107
  limit
11012
11108
  );
11013
- const noteOff = Math.min(Math.max(decayEnd, _when + duration), limit);
11014
- const end = isDrum ? _when + buffer.duration - startOffsetSec : Math.max(Math.min(noteOff + env.releaseSec, limit), noteOff + 1e-3);
11109
+ const tailRoom = Math.min(
11110
+ _SoundFont.minReleaseSec,
11111
+ Math.max(0, limit - decayEnd)
11112
+ );
11113
+ const noteOff = Math.min(
11114
+ Math.max(decayEnd, _when + duration),
11115
+ limit - tailRoom
11116
+ );
11117
+ const end = isDrum ? sampleEnd : Math.max(Math.min(noteOff + env.releaseSec, limit), noteOff + tailRoom);
11015
11118
  if (!isDrum) {
11016
11119
  const sustainVolume = effectiveVolume * env.sustain;
11017
11120
  g.gain.linearRampToValueAtTime(effectiveVolume, attackEnd);
@@ -11020,6 +11123,11 @@ var SoundFont = class _SoundFont {
11020
11123
  g.gain.linearRampToValueAtTime(0, end);
11021
11124
  } else {
11022
11125
  g.gain.linearRampToValueAtTime(effectiveVolume, attackEnd);
11126
+ const releaseStart = Math.max(attackEnd, end - _SoundFont.minReleaseSec);
11127
+ if (releaseStart > attackEnd) {
11128
+ g.gain.setValueAtTime(effectiveVolume, releaseStart);
11129
+ }
11130
+ g.gain.linearRampToValueAtTime(0, end);
11023
11131
  }
11024
11132
  if (filter) src.connect(filter).connect(g);
11025
11133
  else src.connect(g);
@@ -11229,12 +11337,10 @@ var addParam = (zone, pitch) => {
11229
11337
  coarseTune,
11230
11338
  fineTune,
11231
11339
  sampleRate,
11232
- delay,
11233
- buffer
11340
+ delay
11234
11341
  } = zone;
11235
11342
  const baseDetune = originalPitch - 100 * coarseTune - fineTune;
11236
11343
  const playbackRate = 2 ** ((100 * pitch - baseDetune) / 1200);
11237
- const max = (buffer?.duration ?? 0) / playbackRate;
11238
11344
  const src = {
11239
11345
  loop: loopStart >= 1 && loopStart < loopEnd
11240
11346
  };
@@ -11242,7 +11348,7 @@ var addParam = (zone, pitch) => {
11242
11348
  [src.loopStart, src.loopEnd] = [loopStart, loopEnd].map(
11243
11349
  (v) => v / sampleRate + delay
11244
11350
  );
11245
- zone._param = { playbackRate, max, src };
11351
+ zone._param = { playbackRate, src };
11246
11352
  };
11247
11353
 
11248
11354
  // src/chord-player.ts
@@ -12540,29 +12646,30 @@ var CORPUS_BANDS = {
12540
12646
  climaxPeaks: [1, 2, 13.5, 26],
12541
12647
  complementarity: [0, 0.026, 0.179, 0.405]
12542
12648
  };
12543
- var CORPUS_MEDIANS = {
12544
- entropy: 0.907,
12545
- valueKinds: 4,
12546
- restRatio: 0.149,
12547
- leapRatio: 0.339,
12548
- stepRatio: 0.538,
12549
- chromaticRatio: 0.052,
12550
- maxLeap: 12,
12551
- melodyRange: 20,
12552
- notesPerBar: 5.511,
12553
- shortNoteRatio: 0.816,
12554
- barDensityCv: 0.279,
12555
- densityCliff: 0.026,
12556
- sim1: 0.518,
12557
- sim2: 0.552,
12558
- sim4: 0.616,
12559
- sim8: 0.676,
12560
- phraseBreath: 0.227,
12561
- turnRatio: 0.519,
12562
- climaxPosition: 0.273,
12563
- climaxPeaks: 6,
12564
- complementarity: 0.075
12565
- };
12649
+ var CORPUS_PROFILE_KEYS = [
12650
+ "entropy",
12651
+ "valueKinds",
12652
+ "restRatio",
12653
+ "leapRatio",
12654
+ "stepRatio",
12655
+ "chromaticRatio",
12656
+ "maxLeap",
12657
+ "melodyRange",
12658
+ "notesPerBar",
12659
+ "shortNoteRatio",
12660
+ "barDensityCv",
12661
+ "densityCliff",
12662
+ "sim1",
12663
+ "sim2",
12664
+ "sim4",
12665
+ "sim8",
12666
+ "phraseBreath",
12667
+ "turnRatio",
12668
+ "climaxPosition",
12669
+ "climaxPeaks",
12670
+ "complementarity"
12671
+ ];
12672
+ var CORPUS_DEVIATION_BUDGET = 4;
12566
12673
  var CORPUS_CELL_WEIGHTS = {
12567
12674
  "0,2,4,6,8,10,12,14": 584,
12568
12675
  "0,2,4,6,8,12,14": 512,
@@ -13427,16 +13534,15 @@ var tensionFeatures = (barTension, opts) => {
13427
13534
  return { rise, resolve };
13428
13535
  };
13429
13536
  var band = (v, lo, idealLo, idealHi, hi) => {
13430
- if (v <= lo || v >= hi) return 0;
13431
13537
  if (v >= idealLo && v <= idealHi) return 1;
13538
+ if (v <= lo || v >= hi) return 0;
13432
13539
  if (v < idealLo) return idealLo === lo ? 0 : (v - lo) / (idealLo - lo);
13433
13540
  return hi === idealHi ? 0 : (hi - v) / (hi - idealHi);
13434
13541
  };
13435
- var centeredBand = (v, b, median) => {
13436
- const score = band(v, b[0], b[1], b[2], b[3]);
13437
- if (score < 1) return score;
13438
- const spread = Math.max(median - b[1], b[2] - median, 1e-9);
13439
- return 1 - 0.15 * Math.min(1, Math.abs(v - median) / spread);
13542
+ var plausibleBand = (v, b) => {
13543
+ const span = b[3] - b[0];
13544
+ const margin = span > 0 ? span * 0.5 : 1;
13545
+ return band(v, b[0] - margin, b[0], b[3], b[3] + margin);
13440
13546
  };
13441
13547
  var featureVector = (f) => [
13442
13548
  f.entropy / 3,
@@ -13460,6 +13566,650 @@ var featureDistance = (a, b) => {
13460
13566
  return Math.sqrt(sum);
13461
13567
  };
13462
13568
 
13569
+ // src/compose-scales.ts
13570
+ var MAJOR_SCALE2 = [
13571
+ { semi: 0, fifth: 0 },
13572
+ // C
13573
+ { semi: 2, fifth: 2 },
13574
+ // D
13575
+ { semi: 4, fifth: 4 },
13576
+ // E
13577
+ { semi: 5, fifth: -1 },
13578
+ // F
13579
+ { semi: 7, fifth: 1 },
13580
+ // G
13581
+ { semi: 9, fifth: 3 },
13582
+ // A
13583
+ { semi: 11, fifth: 5 }
13584
+ // B
13585
+ ];
13586
+ var HARMONIC_MINOR_SCALE = [
13587
+ { semi: 0, fifth: 0 },
13588
+ // C
13589
+ { semi: 2, fifth: 2 },
13590
+ // D
13591
+ { semi: 4, fifth: 4 },
13592
+ // E
13593
+ { semi: 5, fifth: -1 },
13594
+ // F
13595
+ { semi: 8, fifth: 8 },
13596
+ // G#
13597
+ { semi: 9, fifth: 3 },
13598
+ // A
13599
+ { semi: 11, fifth: 5 }
13600
+ // B
13601
+ ];
13602
+ var HUNGARIAN_SCALE = [
13603
+ { semi: 0, fifth: 0 },
13604
+ // C
13605
+ { semi: 3, fifth: 9 },
13606
+ // D#
13607
+ { semi: 4, fifth: 4 },
13608
+ // E
13609
+ { semi: 5, fifth: -1 },
13610
+ // F
13611
+ { semi: 8, fifth: 8 },
13612
+ // G#
13613
+ { semi: 9, fifth: 3 },
13614
+ // A
13615
+ { semi: 11, fifth: 5 }
13616
+ // B
13617
+ ];
13618
+ var BLUES_SCALE = [
13619
+ { semi: 0, fifth: 0 },
13620
+ // C
13621
+ { semi: 3, fifth: -3 },
13622
+ // Eb
13623
+ { semi: 5, fifth: -1 },
13624
+ // F
13625
+ { semi: 6, fifth: -6 },
13626
+ // Gb(ブルーノート)
13627
+ { semi: 7, fifth: 1 },
13628
+ // G
13629
+ { semi: 10, fifth: -2 }
13630
+ // Bb
13631
+ ];
13632
+ var RYUKYU_CENTER = {
13633
+ tonic: "C",
13634
+ half: "G",
13635
+ deceptive: "Em",
13636
+ tonicPattern: /^C(?![#b]|m)/,
13637
+ a: [
13638
+ ["C", "F", "G", "C"],
13639
+ ["C", "C", "F", "G"],
13640
+ ["F", "G", "C", "C"],
13641
+ ["C", "G", "F", "G"],
13642
+ ["CM7", "F", "G", "C"],
13643
+ ["C", "F", "C", "G"],
13644
+ ["C", "Em", "F", "G"],
13645
+ ["F", "C", "G", "C"]
13646
+ ],
13647
+ b: [
13648
+ ["F", "G", "Em", "C"],
13649
+ ["F", "G", "C", "G"],
13650
+ ["G", "F", "C", "C"],
13651
+ ["FM7", "G", "Em", "F"],
13652
+ ["C", "G", "F", "C"],
13653
+ ["F", "Em", "F", "G"]
13654
+ ],
13655
+ c: [
13656
+ ["Em", "F", "G", "C"],
13657
+ ["F", "C", "G", "Em"],
13658
+ ["C", "Em", "F", "G"]
13659
+ ]
13660
+ };
13661
+ var MIYAKOBUSHI_CENTER = {
13662
+ tonic: "Em",
13663
+ half: "Am",
13664
+ deceptive: "F",
13665
+ tonicPattern: /^E(?:m|sus)/,
13666
+ a: [
13667
+ ["Em", "F", "Em", "Em"],
13668
+ ["Esus4", "F", "Esus4", "Em"],
13669
+ ["Em", "Am", "F", "Em"],
13670
+ ["Am", "Em", "F", "Em"],
13671
+ ["Em", "F", "Am", "Em"],
13672
+ ["Em", "Em", "F", "F"],
13673
+ ["Am", "F", "Em", "Em"],
13674
+ ["Em", "FM7", "Am", "Em"]
13675
+ ],
13676
+ b: [
13677
+ ["F", "Am", "Em", "Em"],
13678
+ ["Am", "F", "Em", "Em"],
13679
+ ["F", "Em", "Am", "Em"],
13680
+ ["FM7", "Am", "F", "Em"],
13681
+ ["Am", "Em", "F", "Am"],
13682
+ ["F", "Am", "F", "Em"]
13683
+ ],
13684
+ c: [
13685
+ ["Am", "Em", "F", "Am"],
13686
+ ["F", "Am", "Esus4", "Em"],
13687
+ ["Am", "F", "Em", "Em"]
13688
+ ]
13689
+ };
13690
+ var RITSU_CENTER = {
13691
+ tonic: "Dsus4",
13692
+ half: "G",
13693
+ deceptive: "Em",
13694
+ tonicPattern: /^Dsus/,
13695
+ a: [
13696
+ ["Dsus4", "G", "Dsus4", "Dsus4"],
13697
+ ["Dsus4", "Em", "G", "Dsus4"],
13698
+ ["G", "Dsus4", "Em", "Dsus4"],
13699
+ ["Dsus4", "G", "Em", "G"],
13700
+ ["Em", "G", "Dsus4", "Dsus4"],
13701
+ ["Dsus4", "Em7", "G", "Dsus4"],
13702
+ ["G", "Em", "Dsus4", "G"],
13703
+ ["Dsus4", "Am", "G", "Dsus4"]
13704
+ ],
13705
+ b: [
13706
+ ["G", "Em", "Dsus4", "Dsus4"],
13707
+ ["Em", "G", "Em", "Dsus4"],
13708
+ ["G", "Am", "Em", "Dsus4"],
13709
+ ["Em7", "G", "Dsus4", "Dsus4"],
13710
+ ["Am", "G", "Em", "Dsus4"],
13711
+ ["Dsus4", "Em", "G", "Em"]
13712
+ ],
13713
+ c: [
13714
+ ["Em", "Dsus4", "G", "Em"],
13715
+ ["G", "Em7", "Am", "G"],
13716
+ ["Am", "G", "Em", "Dsus4"]
13717
+ ]
13718
+ };
13719
+ var HARMONIC_MINOR_CENTER = {
13720
+ tonic: "Am",
13721
+ half: "E7",
13722
+ deceptive: "F",
13723
+ tonicPattern: /^Am/,
13724
+ a: [
13725
+ ["Am", "Dm", "E7", "Am"],
13726
+ // i-iv-V7-i。和声的短音階の基本形
13727
+ ["Am", "F", "E7", "Am"],
13728
+ ["Am", "E7", "Am", "Am"],
13729
+ ["Dm", "E7", "Am", "Am"],
13730
+ ["Am", "AmM7", "Dm", "E7"],
13731
+ // 主和音に導音を重ねたクリシェ
13732
+ ["Am", "F", "Dm", "E7"],
13733
+ ["F", "E7", "Am", "Am"],
13734
+ ["Am", "Bm7-5", "E7", "Am"]
13735
+ ],
13736
+ b: [
13737
+ ["Dm", "E7", "Am", "Am"],
13738
+ ["F", "E7", "Am", "E7"],
13739
+ ["Dm7", "G#dim", "Am", "E7"],
13740
+ ["F", "Dm", "E7", "Am"],
13741
+ ["Am", "Dm", "Bm7-5", "E7"],
13742
+ ["FM7", "E7", "Am", "Am"]
13743
+ ],
13744
+ c: [
13745
+ ["Dm", "Am", "Bm7-5", "E7"],
13746
+ ["F", "C+", "Dm", "E7"],
13747
+ ["Am", "F", "Dm", "E7"]
13748
+ ]
13749
+ };
13750
+ var HIJAZ_CENTER = {
13751
+ tonic: "E",
13752
+ half: "F",
13753
+ deceptive: "Am",
13754
+ tonicPattern: /^E(?![#b]|m)/,
13755
+ a: [
13756
+ ["E", "F", "E", "E"],
13757
+ // I-♭II。ヒジャーズの顔
13758
+ ["E7", "F", "E", "E"],
13759
+ ["Am", "F", "E", "E"],
13760
+ ["E", "F", "Dm", "E"],
13761
+ ["F", "E", "F", "E"],
13762
+ ["Dm", "E", "F", "E"],
13763
+ ["E7", "Am", "F", "E"],
13764
+ ["E", "Dm", "F", "E"]
13765
+ ],
13766
+ b: [
13767
+ ["F", "E", "Am", "E"],
13768
+ ["Dm", "C+", "F", "E"],
13769
+ ["Am", "Dm", "F", "E"],
13770
+ ["F", "Dm", "E", "E"],
13771
+ ["E7", "F", "Dm", "E"],
13772
+ ["Bm7-5", "E7", "Am", "E"]
13773
+ ],
13774
+ c: [
13775
+ ["Am", "Dm", "F", "E"],
13776
+ ["Dm", "Am", "F", "E7"],
13777
+ ["F", "C+", "Dm", "E"]
13778
+ ]
13779
+ };
13780
+ var HUNGARIAN_CENTER = {
13781
+ tonic: "Am",
13782
+ half: "E",
13783
+ deceptive: "F",
13784
+ tonicPattern: /^Am/,
13785
+ a: [
13786
+ ["Am", "E", "Am", "Am"],
13787
+ ["Am", "F", "E", "Am"],
13788
+ ["Am", "AmM7", "F", "E"],
13789
+ ["F", "E", "Am", "Am"],
13790
+ ["Am", "Fm", "E", "Am"],
13791
+ // ♭VIm。増2度を和音の側でも鳴らす
13792
+ ["Am", "E", "F", "E"],
13793
+ ["FM7", "E", "Am", "Am"],
13794
+ ["Am", "C+", "F", "E"]
13795
+ ],
13796
+ b: [
13797
+ ["F", "E", "Am", "E"],
13798
+ ["Fm", "E", "Am", "Am"],
13799
+ ["Am", "F", "C+", "E"],
13800
+ ["FM7", "Am", "F", "E"],
13801
+ ["E", "F", "E", "Am"],
13802
+ ["Am", "AmM7", "Fm", "E"]
13803
+ ],
13804
+ c: [
13805
+ ["F", "Am", "Fm", "E"],
13806
+ ["C+", "F", "Am", "E"],
13807
+ ["Am", "Fm", "F", "E"]
13808
+ ]
13809
+ };
13810
+ var BLUES_CENTER = {
13811
+ tonic: "C7",
13812
+ half: "G7",
13813
+ deceptive: "F7",
13814
+ tonicPattern: /^C7/,
13815
+ a: [
13816
+ ["C7", "C7", "C7", "C7"],
13817
+ // 12小節ブルースの1〜4小節
13818
+ ["C7", "F7", "C7", "C7"],
13819
+ ["C7", "C7", "F7", "F7"],
13820
+ ["F7", "F7", "C7", "C7"],
13821
+ // 5〜8小節
13822
+ ["C7", "F7", "C7", "G7"],
13823
+ ["C7", "C7", "G7", "F7"],
13824
+ ["C7", "F7", "G7", "C7"],
13825
+ ["F7", "C7", "G7", "C7"]
13826
+ ],
13827
+ b: [
13828
+ ["F7", "F7", "C7", "C7"],
13829
+ ["G7", "F7", "C7", "C7"],
13830
+ // 9〜12小節(ターンアラウンド)
13831
+ ["F7", "G7", "C7", "C7"],
13832
+ ["C7", "F7", "G7", "C7"],
13833
+ ["F7", "C7", "G7", "F7"],
13834
+ ["G7", "G7", "F7", "C7"]
13835
+ ],
13836
+ c: [
13837
+ ["G7", "F7", "C7", "G7"],
13838
+ ["F7", "F7", "G7", "G7"],
13839
+ ["C7", "C7", "F7", "G7"]
13840
+ ]
13841
+ };
13842
+ var COMPOSE_SCALES = {
13843
+ yo: {
13844
+ id: "yo",
13845
+ label: "\u967D\u97F3\u968E\uFF08\u9577\u8ABF\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF09",
13846
+ tonic: 0,
13847
+ core: [0, 1, 2, 4, 5],
13848
+ // ド レ ミ ソ ラ
13849
+ minorish: false,
13850
+ strict: false,
13851
+ description: "J-POP\u306E\u6A19\u6E96\u3002\u660E\u308B\u304F\u7D20\u76F4\u3067\u6B4C\u3044\u3084\u3059\u3044\u3002\u5F93\u6765\u306E\u9577\u8ABF\u3068\u540C\u3058"
13852
+ },
13853
+ minyo: {
13854
+ id: "minyo",
13855
+ label: "\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF09",
13856
+ tonic: 5,
13857
+ core: [5, 0, 1, 2, 4],
13858
+ // ラ ド レ ミ ソ
13859
+ minorish: true,
13860
+ strict: false,
13861
+ description: "\u308F\u3089\u3079\u6B4C\u30FB\u6C11\u8B21\u306E\u97F3\u968E\u3002\u7FF3\u308A\u304C\u3042\u308B\u304C\u6697\u3059\u304E\u306A\u3044\u3002\u5F93\u6765\u306E\u77ED\u8ABF\u3068\u540C\u3058"
13862
+ },
13863
+ ritsu: {
13864
+ id: "ritsu",
13865
+ label: "\u5F8B\u97F3\u968E",
13866
+ tonic: 1,
13867
+ core: [1, 2, 4, 5, 6],
13868
+ // レ ミ ソ ラ シ
13869
+ minorish: true,
13870
+ strict: true,
13871
+ center: RITSU_CENTER,
13872
+ description: "\u96C5\u697D\u30FB\u58F0\u660E\u306E\u97F3\u968E\u3002\u534A\u97F3\u3092\u542B\u307E\u305A\u3001\u5E73\u3089\u3067\u8358\u91CD\u306B\u6D41\u308C\u308B"
13873
+ },
13874
+ miyakobushi: {
13875
+ id: "miyakobushi",
13876
+ label: "\u90FD\u7BC0\u97F3\u968E\uFF08\u9670\u97F3\u968E\uFF09",
13877
+ tonic: 2,
13878
+ core: [2, 3, 5, 6, 0],
13879
+ // ミ ファ ラ シ ド
13880
+ minorish: true,
13881
+ strict: true,
13882
+ center: MIYAKOBUSHI_CENTER,
13883
+ description: "\u300E\u3055\u304F\u3089\u3055\u304F\u3089\u300F\u306E\u97F3\u968E\u3002\u4E3B\u97F3\u306E\u3059\u3050\u4E0A\u304C\u534A\u97F3\u3067\u3001\u7FF3\u308A\u304C\u6FC3\u3044"
13884
+ },
13885
+ ryukyu: {
13886
+ id: "ryukyu",
13887
+ label: "\u7409\u7403\u97F3\u968E",
13888
+ tonic: 0,
13889
+ core: [0, 2, 3, 4, 6],
13890
+ // ド ミ ファ ソ シ
13891
+ minorish: false,
13892
+ strict: true,
13893
+ center: RYUKYU_CENTER,
13894
+ description: "\u6C96\u7E04\u97F3\u968E\u3002\u30EC\u3068\u30E9\u3092\u629C\u304D\u3001\u30D5\u30A1\u3068\u30B7\u3092\u67F1\u306B\u3059\u308B\u3002\u660E\u308B\u304F\u8DF3\u306D\u308B"
13895
+ },
13896
+ dorian: {
13897
+ id: "dorian",
13898
+ label: "\u30C9\u30EA\u30A2\u30F3",
13899
+ tonic: 1,
13900
+ core: [1, 3, 4, 5, 0],
13901
+ // レ ファ ソ ラ ド
13902
+ minorish: true,
13903
+ strict: false,
13904
+ description: "\u77ED\u8ABF\u3060\u304C6\u5EA6\u304C\u660E\u308B\u3044\u3002\u30B1\u30EB\u30C8\u30FB\u30ED\u30C3\u30AF\u30FB\u30B7\u30C6\u30A3\u30DD\u30C3\u30D7"
13905
+ },
13906
+ phrygian: {
13907
+ id: "phrygian",
13908
+ label: "\u30D5\u30EA\u30B8\u30A2\u30F3",
13909
+ tonic: 2,
13910
+ core: [2, 3, 5, 6, 1],
13911
+ // ミ ファ ラ シ レ
13912
+ minorish: true,
13913
+ strict: false,
13914
+ description: "\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3002\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\uFF0F\u30E1\u30BF\u30EB\u306E\u7DCA\u8FEB\u3057\u305F\u97FF\u304D"
13915
+ },
13916
+ lydian: {
13917
+ id: "lydian",
13918
+ label: "\u30EA\u30C7\u30A3\u30A2\u30F3",
13919
+ tonic: 3,
13920
+ core: [3, 4, 6, 0, 2],
13921
+ // ファ ソ シ ド ミ
13922
+ minorish: false,
13923
+ strict: false,
13924
+ description: "4\u5EA6\u304C\u9AD8\u304F\u3001\u6D6E\u904A\u3057\u3066\u5E83\u304C\u308B\u3002\u6620\u753B\u97F3\u697D\u30FB\u30B2\u30FC\u30E0\u306E\u7A7A\u306E\u8272"
13925
+ },
13926
+ mixolydian: {
13927
+ id: "mixolydian",
13928
+ label: "\u30DF\u30AF\u30BD\u30EA\u30C7\u30A3\u30A2\u30F3",
13929
+ tonic: 4,
13930
+ core: [4, 5, 0, 1, 3],
13931
+ // ソ ラ ド レ ファ
13932
+ minorish: false,
13933
+ strict: false,
13934
+ description: "\u9577\u8ABF\u3060\u304C7\u5EA6\u304C\u4F4E\u3044\u3002\u30D6\u30EB\u30FC\u30B9\u30ED\u30C3\u30AF\u30FB\u6C11\u65CF\u97F3\u697D\u306E\u571F\u304F\u3055\u3055"
13935
+ },
13936
+ harmonic_minor: {
13937
+ id: "harmonic_minor",
13938
+ label: "\u548C\u58F0\u7684\u77ED\u97F3\u968E",
13939
+ tonic: 5,
13940
+ parent: HARMONIC_MINOR_SCALE,
13941
+ core: [5, 0, 1, 2, 4],
13942
+ // ラ ド レ ミ ソ♯
13943
+ minorish: true,
13944
+ strict: false,
13945
+ center: HARMONIC_MINOR_CENTER,
13946
+ description: "\u5C0E\u97F3\u30BD\u266F\u3092\u6301\u3064\u77ED\u8ABF\u3002\u58972\u5EA6\u304C\u6CE3\u304D\u3092\u4F5C\u308B\u3002\u30AF\u30E9\u30B7\u30C3\u30AF\u30FBV\u7CFB\u30FB\u5287\u4F34"
13947
+ },
13948
+ hijaz: {
13949
+ id: "hijaz",
13950
+ label: "\u30D2\u30B8\u30E3\u30FC\u30BA\uFF08\u30D5\u30EA\u30B8\u30A2\u30F3\u30FB\u30C9\u30DF\u30CA\u30F3\u30C8\uFF09",
13951
+ tonic: 2,
13952
+ parent: HARMONIC_MINOR_SCALE,
13953
+ core: [2, 3, 4, 5, 6],
13954
+ // ミ ファ ソ♯ ラ シ
13955
+ minorish: false,
13956
+ strict: false,
13957
+ center: HIJAZ_CENTER,
13958
+ description: "\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3001\u4E3B\u548C\u97F3\u306F\u9577\u4E09\u548C\u97F3\u3002\u4E2D\u6771\u30FB\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\u30FB\u30E1\u30BF\u30EB"
13959
+ },
13960
+ hungarian: {
13961
+ id: "hungarian",
13962
+ label: "\u30CF\u30F3\u30AC\u30EA\u30A2\u30F3\u30FB\u30DE\u30A4\u30CA\u30FC\uFF08\u30B8\u30D7\u30B7\u30FC\uFF09",
13963
+ tonic: 5,
13964
+ parent: HUNGARIAN_SCALE,
13965
+ core: [5, 0, 1, 2, 4],
13966
+ // ラ ド レ♯ ミ ソ♯
13967
+ minorish: true,
13968
+ strict: false,
13969
+ center: HUNGARIAN_CENTER,
13970
+ description: "\u58972\u5EA6\u304C2\u304B\u6240\u3002\u97F3\u968E\u306E\u4E2D\u3067\u3044\u3061\u3070\u3093\u8DF3\u306D\u305F\u3001\u7570\u56FD\u3081\u3044\u305F\u97FF\u304D"
13971
+ },
13972
+ blues: {
13973
+ id: "blues",
13974
+ label: "\u30D6\u30EB\u30FC\u30B9\u97F3\u968E",
13975
+ tonic: 0,
13976
+ parent: BLUES_SCALE,
13977
+ core: [0, 1, 2, 4, 5],
13978
+ // ド ミ♭ ファ ソ シ♭
13979
+ minorish: true,
13980
+ strict: true,
13981
+ center: BLUES_CENTER,
13982
+ description: "\u30D6\u30EB\u30FC\u30CE\u30FC\u30C8\u5165\u308A\u306E6\u97F3\u97F3\u968E\u3002\u77ED3\u5EA6\u3067\u6B4C\u3044\u3001\u4F34\u594F\u306F\u95773\u5EA6\u3067\u9CF4\u308B"
13983
+ }
13984
+ };
13985
+ var COMPOSE_SCALE_IDS = Object.keys(
13986
+ COMPOSE_SCALES
13987
+ );
13988
+ var TONIC_CENTERS = {
13989
+ // --- レ(律・ドリアン) ---
13990
+ 1: {
13991
+ tonic: "Dm",
13992
+ half: "G",
13993
+ deceptive: "F",
13994
+ tonicPattern: /^Dm/,
13995
+ a: [
13996
+ ["Dm", "G", "Dm", "Dm"],
13997
+ // i-IV の往復。ドリアンの顔
13998
+ ["Dm", "Am", "G", "Dm"],
13999
+ ["Dm", "C", "G", "Dm"],
14000
+ ["Dm7", "G", "Dm7", "C"],
14001
+ ["Dm", "F", "C", "G"],
14002
+ ["Dm", "Em", "F", "G"],
14003
+ ["Dm", "G", "F", "C"],
14004
+ ["Dm7", "Em7", "FM7", "G"]
14005
+ ],
14006
+ b: [
14007
+ ["F", "G", "Am", "Dm"],
14008
+ ["C", "G", "Dm", "Dm"],
14009
+ ["G", "F", "C", "Dm"],
14010
+ ["Am", "G", "F", "Dm"],
14011
+ ["FM7", "G", "Em7", "Dm7"],
14012
+ ["Dm", "G", "C", "Am"]
14013
+ ],
14014
+ c: [
14015
+ ["Am", "Dm", "G", "C"],
14016
+ ["F", "Em", "Dm", "G"],
14017
+ ["Dm", "Am", "Em", "G"]
14018
+ ]
14019
+ },
14020
+ // --- ミ(都節・フリジアン) ---
14021
+ 2: {
14022
+ tonic: "Em",
14023
+ half: "Am",
14024
+ deceptive: "C",
14025
+ tonicPattern: /^Em/,
14026
+ a: [
14027
+ ["Em", "F", "Em", "Em"],
14028
+ // i-♭II。フリジアン/都節の顔
14029
+ ["Em", "Am", "F", "Em"],
14030
+ ["Am", "Em", "F", "Em"],
14031
+ ["Em", "F", "G", "Em"],
14032
+ ["Em", "Em", "F", "F"],
14033
+ ["Em", "C", "F", "Em"],
14034
+ ["Am", "F", "Em", "Em"],
14035
+ ["Em7", "FM7", "Em7", "Am7"]
14036
+ ],
14037
+ b: [
14038
+ ["F", "G", "Am", "Em"],
14039
+ ["Am", "G", "F", "Em"],
14040
+ ["F", "Em", "Am", "Em"],
14041
+ ["C", "F", "Em", "Em"],
14042
+ ["FM7", "G", "Em7", "Am"],
14043
+ ["Am", "Em", "F", "G"]
14044
+ ],
14045
+ c: [
14046
+ ["Am", "Em", "F", "C"],
14047
+ ["C", "G", "Am", "Em"],
14048
+ ["F", "C", "Am", "Em"]
14049
+ ]
14050
+ },
14051
+ // --- ファ(リディアン) ---
14052
+ 3: {
14053
+ tonic: "FM7",
14054
+ half: "C",
14055
+ deceptive: "Dm",
14056
+ tonicPattern: /^F(?![#b]|m)/,
14057
+ a: [
14058
+ ["FM7", "G", "FM7", "FM7"],
14059
+ // I-II。リディアンの顔(♯4 が G の3度に居る)
14060
+ ["FM7", "G", "Em", "Am"],
14061
+ ["F", "G", "C", "F"],
14062
+ ["FM7", "G", "Am", "F"],
14063
+ ["F", "C", "G", "F"],
14064
+ ["FM7", "Em7", "Dm7", "G"],
14065
+ ["F", "G", "F", "C"],
14066
+ ["FM7", "G", "Dm7", "F"]
14067
+ ],
14068
+ b: [
14069
+ ["G", "F", "C", "F"],
14070
+ ["Am", "G", "FM7", "FM7"],
14071
+ ["C", "G", "Am", "F"],
14072
+ ["G", "Em", "Am", "F"],
14073
+ ["Dm7", "G", "FM7", "FM7"],
14074
+ ["FM7", "G", "Em7", "F"]
14075
+ ],
14076
+ c: [
14077
+ ["Dm", "Am", "F", "G"],
14078
+ ["Am", "Em", "F", "G"],
14079
+ ["C", "Am", "Dm", "F"]
14080
+ ]
14081
+ },
14082
+ // --- ソ(ミクソリディアン) ---
14083
+ 4: {
14084
+ tonic: "G",
14085
+ half: "Dm",
14086
+ deceptive: "Em",
14087
+ tonicPattern: /^G(?![#b]|m)/,
14088
+ a: [
14089
+ ["G", "F", "C", "G"],
14090
+ // I-♭VII-IV。ミクソリディアンの顔
14091
+ ["G", "C", "F", "G"],
14092
+ ["G", "F", "G", "G"],
14093
+ ["C", "G", "F", "G"],
14094
+ ["G", "Dm", "F", "G"],
14095
+ ["G", "Am", "F", "G"],
14096
+ ["G", "F", "Dm", "C"],
14097
+ ["G", "C", "G", "F"]
14098
+ ],
14099
+ b: [
14100
+ ["F", "C", "G", "G"],
14101
+ ["Am", "F", "C", "G"],
14102
+ ["C", "Dm", "F", "G"],
14103
+ ["Em", "F", "C", "G"],
14104
+ ["F", "G", "Am", "G"],
14105
+ ["Dm7", "F", "C", "G"]
14106
+ ],
14107
+ c: [
14108
+ ["Am", "Em", "F", "G"],
14109
+ ["C", "Am", "Dm", "G"],
14110
+ ["Em", "Am", "F", "C"]
14111
+ ]
14112
+ }
14113
+ };
14114
+ var scaleDegrees = (scale) => scale.parent ?? MAJOR_SCALE2;
14115
+ var scaleSize = (scale) => scaleDegrees(scale).length;
14116
+ var degreeToPitch = (scale, degree) => {
14117
+ const list = scaleDegrees(scale);
14118
+ const size = list.length;
14119
+ const index = (degree % size + size) % size;
14120
+ const octave = Math.floor(degree / size);
14121
+ const d = list[index];
14122
+ return { semi: d.semi + octave * 12, fifth: d.fifth };
14123
+ };
14124
+ var semitoneToDegree = (scale, semi) => {
14125
+ const list = scaleDegrees(scale);
14126
+ const octave = Math.floor(semi / 12);
14127
+ const within = semi - octave * 12;
14128
+ let best = 0;
14129
+ let bestDist = Number.POSITIVE_INFINITY;
14130
+ for (let i2 = 0; i2 < list.length; i2++) {
14131
+ const dist = Math.abs(list[i2].semi - within);
14132
+ if (dist < bestDist) {
14133
+ bestDist = dist;
14134
+ best = i2;
14135
+ }
14136
+ }
14137
+ return octave * list.length + best;
14138
+ };
14139
+ var walk = (scale, semi, delta) => degreeToPitch(scale, semitoneToDegree(scale, semi) + delta).semi;
14140
+ var scaleFifth = (scale, semi) => degreeToPitch(scale, semitoneToDegree(scale, semi)).fifth;
14141
+ var SCALE_PCS = /* @__PURE__ */ new Map();
14142
+ var scalePcs = (scale) => {
14143
+ const hit = SCALE_PCS.get(scale.id);
14144
+ if (hit) return hit;
14145
+ const set = new Set(
14146
+ scaleDegrees(scale).map((d) => (d.semi % 12 + 12) % 12)
14147
+ );
14148
+ SCALE_PCS.set(scale.id, set);
14149
+ return set;
14150
+ };
14151
+ var CORE_SORTED = /* @__PURE__ */ new Map();
14152
+ var sortedCore = (scale) => {
14153
+ const hit = CORE_SORTED.get(scale.id);
14154
+ if (hit) return hit;
14155
+ const sorted = [...scale.core].sort((a, b) => a - b);
14156
+ CORE_SORTED.set(scale.id, sorted);
14157
+ return sorted;
14158
+ };
14159
+ var CORE_PCS = /* @__PURE__ */ new Map();
14160
+ var corePcs = (scale) => {
14161
+ const hit = CORE_PCS.get(scale.id);
14162
+ if (hit) return hit;
14163
+ const list = scaleDegrees(scale);
14164
+ const set = new Set(scale.core.map((d) => (list[d].semi % 12 + 12) % 12));
14165
+ CORE_PCS.set(scale.id, set);
14166
+ return set;
14167
+ };
14168
+ var isCoreDegree = (scale, degree) => {
14169
+ const size = scaleSize(scale);
14170
+ return sortedCore(scale).includes((degree % size + size) % size);
14171
+ };
14172
+ var coreToDegree = (scale, step) => {
14173
+ const sorted = sortedCore(scale);
14174
+ const n = sorted.length;
14175
+ const index = (step % n + n) % n;
14176
+ const octave = Math.floor(step / n);
14177
+ return sorted[index] + octave * scaleSize(scale);
14178
+ };
14179
+ var degreeToCore = (scale, degree) => {
14180
+ const sorted = sortedCore(scale);
14181
+ const size = scaleSize(scale);
14182
+ const index = (degree % size + size) % size;
14183
+ const octave = Math.floor(degree / size);
14184
+ let best = 0;
14185
+ let bestDist = Number.POSITIVE_INFINITY;
14186
+ for (let i2 = 0; i2 < sorted.length; i2++) {
14187
+ const dist = Math.abs(sorted[i2] - index);
14188
+ if (dist < bestDist) {
14189
+ bestDist = dist;
14190
+ best = i2;
14191
+ }
14192
+ }
14193
+ return octave * sorted.length + best;
14194
+ };
14195
+ var isOutsideCore = (scale, semi) => scale.strict ? !corePcs(scale).has((semi % 12 + 12) % 12) : !isCoreDegree(scale, semitoneToDegree(scale, semi));
14196
+ var resolveComposeScale = (choice, minorKey, rnd = Math.random) => {
14197
+ const c = (choice ?? "").trim() || "auto";
14198
+ const hit = COMPOSE_SCALES[c];
14199
+ if (hit) return hit;
14200
+ if (c === "any")
14201
+ return COMPOSE_SCALES[COMPOSE_SCALE_IDS[Math.floor(rnd() * COMPOSE_SCALE_IDS.length)] ?? "yo"];
14202
+ return COMPOSE_SCALES[minorKey ? "minyo" : "yo"];
14203
+ };
14204
+ var getComposeScaleDescription = (choice) => {
14205
+ const hit = COMPOSE_SCALES[choice];
14206
+ if (hit) return hit.description;
14207
+ if (choice === "any")
14208
+ return `${COMPOSE_SCALE_IDS.length}\u3064\u306E\u97F3\u968E\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059`;
14209
+ return "\u30D9\u30FC\u30B9\u8ABF\u306E\u9577\u77ED\u306B\u5408\u308F\u305B\u3066\u3001\u967D\u97F3\u968E\uFF08\u9577\u8ABF\uFF09\u304B\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\uFF09\u3092\u4F7F\u3044\u307E\u3059";
14210
+ };
14211
+ var resolveCenter = (scale) => scale.center ?? TONIC_CENTERS[scale.tonic] ?? null;
14212
+
13463
14213
  // src/compose-sections.ts
13464
14214
  var SECTION_LABELS = {
13465
14215
  intro: "\u30A4\u30F3\u30C8\u30ED",
@@ -13695,6 +14445,7 @@ var sectionAt = (plan, bar) => {
13695
14445
  // src/compose.ts
13696
14446
  var STEP_SEMITONES = 2;
13697
14447
  var MAX_LEAP_SEMITONES = 10;
14448
+ var LEAP_CEILINGS = [7, 8, 9, 10, 10, 12, 14, 16];
13698
14449
  var MAX_BAR_LEAP_SEMITONES = 10;
13699
14450
  var HARD = {
13700
14451
  /** メロディが1音も無い、音域が半音未満(同じ音を並べただけ)。 */
@@ -13753,13 +14504,16 @@ var WEIGHTS = {
13753
14504
  // --- 直近に作った曲と違うか ---
13754
14505
  novelty: 1.2
13755
14506
  };
14507
+ var DEVIATION_BUDGET = CORPUS_DEVIATION_BUDGET;
14508
+ var BUDGETED_KEYS = new Set(CORPUS_PROFILE_KEYS);
13756
14509
  var HAND_BANDS = {
13757
14510
  /** サブメロの音数/小節。少なすぎると「置いただけ」、多すぎるとメロディを食う。 */
13758
14511
  subDensity: [0.5, 1.8, 4.5, 8],
13759
14512
  complementarity: [0.05, 0.25, 0.7, 0.95],
13760
14513
  climaxPeaks: [0, 1, 2, 5]
13761
14514
  };
13762
- var DRAW_COUNT = 40;
14515
+ var DRAW_COUNT = 12;
14516
+ var SELECT_TEMPERATURE = 0.05;
13763
14517
  var BASE_STEPS_PER_BAR = 192;
13764
14518
  var WHOLE = 192;
13765
14519
  var DOT_HALF = 144;
@@ -13884,65 +14638,6 @@ var BPM_CHOICES = [
13884
14638
  180,
13885
14639
  185
13886
14640
  ];
13887
- var MAJOR_SCALE2 = [
13888
- { semi: 0, fifth: 0 },
13889
- // C
13890
- { semi: 2, fifth: 2 },
13891
- // D
13892
- { semi: 4, fifth: 4 },
13893
- // E
13894
- { semi: 5, fifth: -1 },
13895
- // F
13896
- { semi: 7, fifth: 1 },
13897
- // G
13898
- { semi: 9, fifth: 3 },
13899
- // A
13900
- { semi: 11, fifth: 5 }
13901
- // B
13902
- ];
13903
- var degreeToPitch = (degree) => {
13904
- const index = (degree % 7 + 7) % 7;
13905
- const octave = Math.floor(degree / 7);
13906
- const d = MAJOR_SCALE2[index];
13907
- return { semi: d.semi + octave * 12, fifth: d.fifth };
13908
- };
13909
- var semitoneToDegree = (semi) => {
13910
- const octave = Math.floor(semi / 12);
13911
- const within = semi - octave * 12;
13912
- let best = 0;
13913
- let bestDist = Number.POSITIVE_INFINITY;
13914
- for (let i2 = 0; i2 < MAJOR_SCALE2.length; i2++) {
13915
- const dist = Math.abs(MAJOR_SCALE2[i2].semi - within);
13916
- if (dist < bestDist) {
13917
- bestDist = dist;
13918
- best = i2;
13919
- }
13920
- }
13921
- return octave * 7 + best;
13922
- };
13923
- var walk = (semi, delta) => degreeToPitch(semitoneToDegree(semi) + delta).semi;
13924
- var NON_PENTATONIC_DEGREES = /* @__PURE__ */ new Set([3, 6]);
13925
- var isNonPentatonic = (semi) => NON_PENTATONIC_DEGREES.has((semitoneToDegree(semi) % 7 + 7) % 7);
13926
- var PENTATONIC_DEGREES = [0, 1, 2, 4, 5];
13927
- var pentaToDegree = (penta) => {
13928
- const index = (penta % 5 + 5) % 5;
13929
- const octave = Math.floor(penta / 5);
13930
- return PENTATONIC_DEGREES[index] + octave * 7;
13931
- };
13932
- var degreeToPenta = (degree) => {
13933
- const index = (degree % 7 + 7) % 7;
13934
- const octave = Math.floor(degree / 7);
13935
- let best = 0;
13936
- let bestDist = Number.POSITIVE_INFINITY;
13937
- for (let i2 = 0; i2 < PENTATONIC_DEGREES.length; i2++) {
13938
- const dist = Math.abs(PENTATONIC_DEGREES[i2] - index);
13939
- if (dist < bestDist) {
13940
- bestDist = dist;
13941
- best = i2;
13942
- }
13943
- }
13944
- return octave * 5 + best;
13945
- };
13946
14641
  var SECTION_A_PROGRESSIONS = [
13947
14642
  ["C", "G", "Am", "Em7"],
13948
14643
  // カノン進行の前半
@@ -14061,6 +14756,22 @@ var SECTION_DECEPTIVE_DERIVATIONS = [
14061
14756
  (a, t) => [a[0], "Dm7", "G7", t === "Am" ? "FM7" : "Am7"],
14062
14757
  (a, t) => [a[0], "F", "G7", t === "Am" ? "F" : "Am7"]
14063
14758
  ];
14759
+ var MODAL_FULL_DERIVATIONS = [
14760
+ (a, c) => [a[0], a[1], c.half, c.tonic],
14761
+ (a, c) => [a[0], c.half, a[2], c.tonic],
14762
+ (a, c) => [a[0], a[1], a[2], c.tonic],
14763
+ (a, c) => [c.tonic, c.half, a[2], c.tonic]
14764
+ ];
14765
+ var MODAL_HALF_DERIVATIONS = [
14766
+ (a, c) => [a[0], a[1], a[2], c.half],
14767
+ (a, c) => [a[0], a[1], c.tonic, c.half],
14768
+ (a, c) => [a[0], c.tonic, a[2], c.half]
14769
+ ];
14770
+ var MODAL_DECEPTIVE_DERIVATIONS = [
14771
+ (a, c) => [a[0], a[1], c.half, c.deceptive],
14772
+ (a, c) => [a[0], c.half, a[2], c.deceptive],
14773
+ (a, c) => [a[0], a[1], a[2], c.deceptive]
14774
+ ];
14064
14775
  var MODAL_BORROW = {
14065
14776
  F: "Fm",
14066
14777
  // IV → IVm(サブドミナントマイナー。いちばん定番)
@@ -14518,6 +15229,14 @@ for (const c of [...MOTIF_CELLS, ...RHYTHM_CELLS]) {
14518
15229
  const key = onsetKeyOf(c.value);
14519
15230
  cellEntryCount.set(key, (cellEntryCount.get(key) ?? 0) + 1);
14520
15231
  }
15232
+ var resolveMelodyForm = (choice, rnd) => {
15233
+ const c = (choice ?? "").trim() || "auto";
15234
+ if (c === "motif" || c === "ostinato" || c === "through") return c;
15235
+ const r = rnd();
15236
+ if (r < 0.25) return "ostinato";
15237
+ if (r < 0.4) return "through";
15238
+ return "motif";
15239
+ };
14521
15240
  var groovyCells = (cells, groove, rnd) => {
14522
15241
  const hasSixteenth = (c) => c.value.some((v) => Math.abs(v) <= SIXTEENTH);
14523
15242
  if (groove === "eighth") {
@@ -14651,7 +15370,7 @@ var nearestChordTone = (targetSemi, tones, minWeight, preferColor = false) => {
14651
15370
  var MELODY_LOW = 60;
14652
15371
  var MELODY_HIGH = 81;
14653
15372
  var MELODY_CENTER = (MELODY_LOW + MELODY_HIGH) / 2;
14654
- var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) => {
15373
+ var harmonyPitch = (scale, melodySemi, tones, prevHarmony, offset, parallel, against) => {
14655
15374
  const lo = Math.max(-12, Math.min(offset, 0) - 7);
14656
15375
  const hi = Math.min(12, Math.max(offset, 0) + 7);
14657
15376
  let best = null;
@@ -14669,7 +15388,8 @@ var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) =
14669
15388
  const g = Math.abs(semi - against) % 12;
14670
15389
  clash = g === 3 || g === 4 || g === 8 || g === 9 || g === 7 ? 0 : g === 0 ? 0.5 : 1.2;
14671
15390
  }
14672
- const cost = stay + clash + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
15391
+ const offCore = scale.strict && isOutsideCore(scale, semi) ? 1.5 : 0;
15392
+ const cost = stay + clash + offCore + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
14673
15393
  if (cost < bestCost) {
14674
15394
  bestCost = cost;
14675
15395
  best = { semi, fifth: tone.fifth };
@@ -14692,7 +15412,7 @@ var clampSemi = (semi, low, high) => {
14692
15412
  while (s > high) s -= 12;
14693
15413
  return s;
14694
15414
  };
14695
- var leapTarget = (from, tones, rnd) => {
15415
+ var leapTarget = (from, tones, rnd, maxLeap = MAX_LEAP_SEMITONES) => {
14696
15416
  const candidates = [];
14697
15417
  for (const tone of tones) {
14698
15418
  const base = pitchClass(tone.semi);
@@ -14700,7 +15420,7 @@ var leapTarget = (from, tones, rnd) => {
14700
15420
  const semi = base + oct * 12;
14701
15421
  if (semi < MELODY_LOW || semi > MELODY_HIGH) continue;
14702
15422
  const gap = Math.abs(semi - from);
14703
- if (gap >= 3 && gap <= MAX_LEAP_SEMITONES) candidates.push(semi);
15423
+ if (gap >= 3 && gap <= maxLeap) candidates.push(semi);
14704
15424
  }
14705
15425
  }
14706
15426
  if (candidates.length === 0) return null;
@@ -14714,40 +15434,43 @@ var leapTarget = (from, tones, rnd) => {
14714
15434
  ];
14715
15435
  return pick(weighted.length > 0 ? weighted : candidates, rnd);
14716
15436
  };
14717
- var landOn = (degrees, scaleIndex) => {
15437
+ var landOn = (scale, degrees, scaleIndex) => {
14718
15438
  if (degrees.length === 0) return;
15439
+ const size = scaleSize(scale);
14719
15440
  const last = degrees[degrees.length - 1];
14720
- const index = (last % 7 + 7) % 7;
15441
+ const index = (last % size + size) % size;
14721
15442
  let delta = scaleIndex - index;
14722
- if (delta > 3) delta -= 7;
14723
- if (delta < -3) delta += 7;
15443
+ if (delta > size / 2) delta -= size;
15444
+ if (delta < -size / 2) delta += size;
14724
15445
  degrees[degrees.length - 1] = last + delta;
14725
15446
  };
14726
- var landPitch = (pitches, scaleIndex) => {
15447
+ var landPitch = (scale, pitches, scaleIndex) => {
14727
15448
  if (pitches.length === 0) return;
15449
+ const size = scaleSize(scale);
14728
15450
  const last = pitches[pitches.length - 1];
14729
- const degree = semitoneToDegree(last);
14730
- const index = (degree % 7 + 7) % 7;
15451
+ const degree = semitoneToDegree(scale, last);
15452
+ const index = (degree % size + size) % size;
14731
15453
  let delta = scaleIndex - index;
14732
- if (delta > 3) delta -= 7;
14733
- if (delta < -3) delta += 7;
15454
+ if (delta > size / 2) delta -= size;
15455
+ if (delta < -size / 2) delta += size;
14734
15456
  pitches[pitches.length - 1] = clampSemi(
14735
- degreeToPitch(degree + delta).semi,
15457
+ degreeToPitch(scale, degree + delta).semi,
14736
15458
  MELODY_LOW,
14737
15459
  MELODY_HIGH
14738
15460
  );
14739
15461
  };
14740
- var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourOffset, repeatShift, barHeadWeight, preferColor, quarterSteps, rnd) => {
15462
+ var barDegrees = (role, slots, tones, style, scale, motifContour, startDegree, contourOffset, repeatShift, barHeadWeight, preferColor, quarterSteps, rnd) => {
14741
15463
  const noteCount = slots.length;
14742
15464
  const out = [];
14743
15465
  if (role === "motif" || role === "sequence" || role === "climax" || role === "answer") {
14744
15466
  const shift = (role === "sequence" ? pick([-2, -1, 1, 2], rnd) : role === "climax" ? 5 : 0) + repeatShift;
14745
15467
  if (style.pentatonicMotif) {
14746
- const startPenta = degreeToPenta(startDegree);
15468
+ const startCore = degreeToCore(scale, startDegree);
14747
15469
  for (let i2 = 0; i2 < noteCount; i2++)
14748
15470
  out.push(
14749
- pentaToDegree(
14750
- startPenta + shift + motifContour[(contourOffset + i2) % motifContour.length]
15471
+ coreToDegree(
15472
+ scale,
15473
+ startCore + shift + motifContour[(contourOffset + i2) % motifContour.length]
14751
15474
  )
14752
15475
  );
14753
15476
  } else {
@@ -14767,9 +15490,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14767
15490
  for (let i2 = 0; i2 < noteCount; i2++)
14768
15491
  out.push(startDegree + dir2 * (i2 < peak ? i2 : peak * 2 - i2 - 1));
14769
15492
  } else if (style.runShape === "broken") {
14770
- const arp = tones.map((t) => semitoneToDegree(clampSemi(t.semi, 60, 71))).sort((a, b) => a - b);
15493
+ const arp = tones.map((t) => semitoneToDegree(scale, clampSemi(t.semi, 60, 71))).sort((a, b) => a - b);
14771
15494
  for (let i2 = 0; i2 < noteCount; i2++) {
14772
- const oct = Math.floor(i2 / arp.length) * 7;
15495
+ const oct = Math.floor(i2 / arp.length) * scaleSize(scale);
14773
15496
  const idx = dir2 > 0 ? i2 % arp.length : arp.length - 1 - i2 % arp.length;
14774
15497
  out.push(arp[idx] + dir2 * oct);
14775
15498
  }
@@ -14792,7 +15515,10 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14792
15515
  return out;
14793
15516
  }
14794
15517
  if (role === "cadence") {
14795
- const tonic = semitoneToDegree(clampSemi(72, MELODY_LOW, MELODY_HIGH));
15518
+ const tonic = semitoneToDegree(
15519
+ scale,
15520
+ clampSemi(72, MELODY_LOW, MELODY_HIGH)
15521
+ );
14796
15522
  for (let i2 = 0; i2 < noteCount; i2++) {
14797
15523
  if (style.cadenceShape === "descend") {
14798
15524
  out.push(tonic + noteCount - 1 - i2);
@@ -14800,7 +15526,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14800
15526
  const shape = [4, 2, 0];
14801
15527
  out.push(tonic + shape[Math.min(i2, shape.length - 1)]);
14802
15528
  } else if (style.cadenceShape === "leap-up") {
14803
- out.push(i2 === noteCount - 1 ? tonic : tonic - 7 + Math.min(i2, 4));
15529
+ out.push(
15530
+ i2 === noteCount - 1 ? tonic : tonic - scaleSize(scale) + Math.min(i2, 4)
15531
+ );
14804
15532
  } else {
14805
15533
  out.push(i2 === 0 && noteCount > 1 ? tonic + 1 : tonic);
14806
15534
  }
@@ -14835,8 +15563,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14835
15563
  for (let i2 = 0; i2 < out.length; i2++) {
14836
15564
  if (!slots[i2].isStrong) continue;
14837
15565
  out[i2] = semitoneToDegree(
15566
+ scale,
14838
15567
  nearestChordTone(
14839
- degreeToPitch(out[i2]).semi,
15568
+ degreeToPitch(scale, out[i2]).semi,
14840
15569
  tones,
14841
15570
  barHeadWeight,
14842
15571
  preferColor
@@ -14847,9 +15576,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14847
15576
  if (slots[i2].isStrong) continue;
14848
15577
  if (slots[i2].value >= quarterSteps) {
14849
15578
  if (rnd() < style.leapAffinity) {
14850
- const from = degreeToPitch(out[i2 - 1]).semi;
14851
- const target = leapTarget(from, tones, rnd);
14852
- if (target !== null) out[i2] = semitoneToDegree(target);
15579
+ const from = degreeToPitch(scale, out[i2 - 1]).semi;
15580
+ const target = leapTarget(from, tones, rnd, style.maxLeap);
15581
+ if (target !== null) out[i2] = semitoneToDegree(scale, target);
14853
15582
  }
14854
15583
  continue;
14855
15584
  }
@@ -14859,20 +15588,20 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14859
15588
  }
14860
15589
  return out;
14861
15590
  };
14862
- var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, preferShift) => {
15591
+ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, scale, pentatonic, preferShift, maxShift = 3) => {
14863
15592
  let best = degrees;
14864
15593
  let bestShift = 0;
14865
15594
  let bestScore = Number.NEGATIVE_INFINITY;
14866
15595
  let preferScore = Number.NEGATIVE_INFINITY;
14867
15596
  let preferMoved = null;
14868
- for (let shift = -3; shift <= 3; shift++) {
15597
+ for (let shift = -maxShift; shift <= maxShift; shift++) {
14869
15598
  const moved = degrees.map(
14870
- (d) => pentatonic ? pentaToDegree(degreeToPenta(d) + shift) : d + shift
15599
+ (d) => pentatonic ? coreToDegree(scale, degreeToCore(scale, d) + shift) : d + shift
14871
15600
  );
14872
15601
  let score = 0;
14873
15602
  for (let i2 = 0; i2 < moved.length; i2++) {
14874
15603
  const semi = clampSemi(
14875
- degreeToPitch(moved[i2]).semi,
15604
+ degreeToPitch(scale, moved[i2]).semi,
14876
15605
  MELODY_LOW,
14877
15606
  MELODY_HIGH
14878
15607
  );
@@ -14881,7 +15610,7 @@ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, prefe
14881
15610
  score += important ? w * 3 : w;
14882
15611
  }
14883
15612
  const head = clampSemi(
14884
- degreeToPitch(moved[0]).semi,
15613
+ degreeToPitch(scale, moved[0]).semi,
14885
15614
  MELODY_LOW,
14886
15615
  MELODY_HIGH
14887
15616
  );
@@ -14904,7 +15633,7 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
14904
15633
  const out = [];
14905
15634
  let prev = prevSemi;
14906
15635
  if (opts.preserveContour) {
14907
- const raw = degrees.map((d) => degreeToPitch(d).semi);
15636
+ const raw = degrees.map((d) => degreeToPitch(opts.scale, d).semi);
14908
15637
  const lo = Math.min(...raw);
14909
15638
  const hi = Math.max(...raw);
14910
15639
  let shift = 0;
@@ -14918,30 +15647,42 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
14918
15647
  }
14919
15648
  for (let i2 = 0; i2 < degrees.length; i2++) {
14920
15649
  let semi = clampSemi(
14921
- degreeToPitch(degrees[i2]).semi,
15650
+ degreeToPitch(opts.scale, degrees[i2]).semi,
14922
15651
  MELODY_LOW,
14923
15652
  MELODY_HIGH
14924
15653
  );
14925
- const limit = i2 === 0 ? MAX_BAR_LEAP_SEMITONES : MAX_LEAP_SEMITONES;
15654
+ const limit = i2 === 0 ? MAX_BAR_LEAP_SEMITONES : opts.maxLeap;
14926
15655
  if (!opts.allowLeap && Math.abs(semi - prev) > limit) {
14927
15656
  semi = clampSemi(
14928
- walk(prev, Math.sign(semi - prev) * 3),
15657
+ walk(opts.scale, prev, Math.sign(semi - prev) * 3),
14929
15658
  MELODY_LOW,
14930
15659
  MELODY_HIGH
14931
15660
  );
14932
15661
  }
14933
15662
  if (!opts.allowArpeggio && i2 >= 2 && Math.abs(out[i2 - 1] - out[i2 - 2]) > STEP_SEMITONES) {
14934
15663
  const back = -Math.sign(out[i2 - 1] - out[i2 - 2]);
14935
- semi = clampSemi(walk(out[i2 - 1], back), MELODY_LOW, MELODY_HIGH);
15664
+ semi = clampSemi(
15665
+ walk(opts.scale, out[i2 - 1], back),
15666
+ MELODY_LOW,
15667
+ MELODY_HIGH
15668
+ );
14936
15669
  }
14937
15670
  if (toneWeight(semi, tones) === 0 && (slots[i2].isStrong || slots[i2].value >= opts.quarterSteps)) {
14938
15671
  const resolved = semi + 1;
14939
- const useResolved = resolved <= MELODY_HIGH && !DIATONIC_PCS.has(pitchClass(resolved)) && toneWeight(resolved, tones) >= 2 && resolved !== prev && opts.rnd() < opts.chromaticAffinity;
15672
+ const useResolved = resolved <= MELODY_HIGH && !scalePcs(opts.scale).has(pitchClass(resolved)) && toneWeight(resolved, tones) >= 2 && resolved !== prev && opts.rnd() < opts.chromaticAffinity;
14940
15673
  if (useResolved) {
14941
15674
  semi = resolved;
14942
15675
  } else {
14943
- const up = clampSemi(walk(semi, 1), MELODY_LOW, MELODY_HIGH);
14944
- const down = clampSemi(walk(semi, -1), MELODY_LOW, MELODY_HIGH);
15676
+ const up = clampSemi(
15677
+ walk(opts.scale, semi, 1),
15678
+ MELODY_LOW,
15679
+ MELODY_HIGH
15680
+ );
15681
+ const down = clampSemi(
15682
+ walk(opts.scale, semi, -1),
15683
+ MELODY_LOW,
15684
+ MELODY_HIGH
15685
+ );
14945
15686
  const score = (s) => toneWeight(s, tones) * 2 + (i2 > 0 && s === prev ? -3 : 0);
14946
15687
  semi = score(down) >= score(up) ? down : up;
14947
15688
  }
@@ -14949,7 +15690,14 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
14949
15690
  out.push(semi);
14950
15691
  prev = semi;
14951
15692
  }
14952
- applyPentatonic(out, slots, tones, prevSemi, opts.quarterSteps / 2);
15693
+ applyPentatonic(
15694
+ out,
15695
+ slots,
15696
+ tones,
15697
+ prevSemi,
15698
+ opts.quarterSteps / 2,
15699
+ opts.scale
15700
+ );
14953
15701
  applyOctaveJumps(out, slots, opts.octaveAffinity, opts.rnd);
14954
15702
  return out;
14955
15703
  };
@@ -14966,27 +15714,27 @@ var applyOctaveJumps = (out, slots, affinity, rnd) => {
14966
15714
  out[i2] = canUp && (!canDown || rnd() < 0.5) ? up : down;
14967
15715
  }
14968
15716
  };
14969
- var applyPentatonic = (out, slots, tones, prevSemi, shortSteps) => {
15717
+ var applyPentatonic = (out, slots, tones, prevSemi, shortSteps, scale) => {
14970
15718
  for (let i2 = 0; i2 < out.length; i2++) {
14971
15719
  const semi = out[i2];
14972
- if (!isNonPentatonic(semi)) continue;
14973
- if (tones.some((t) => pitchClass(t.semi) === pitchClass(semi))) continue;
15720
+ if (!isOutsideCore(scale, semi)) continue;
15721
+ if (!scale.strict && tones.some((t) => pitchClass(t.semi) === pitchClass(semi)))
15722
+ continue;
14974
15723
  const before = i2 === 0 ? prevSemi : out[i2 - 1];
14975
15724
  const after = i2 + 1 < out.length ? out[i2 + 1] : null;
14976
15725
  const inByStep = Math.abs(semi - before) <= STEP_SEMITONES;
14977
15726
  const outByStep = after !== null && Math.abs(after - semi) <= STEP_SEMITONES;
14978
- if (inByStep || outByStep) continue;
14979
- if (!slots[i2].isStrong && slots[i2].value <= shortSteps) continue;
14980
- const up = clampSemi(walk(semi, 1), MELODY_LOW, MELODY_HIGH);
14981
- const down = clampSemi(walk(semi, -1), MELODY_LOW, MELODY_HIGH);
14982
- const score = (s) => (isNonPentatonic(s) ? -4 : 0) + toneWeight(s, tones) + (s === before ? -3 : 0) + (after !== null ? -Math.abs(after - s) / 12 : 0);
15727
+ if (scale.strict ? inByStep && outByStep : inByStep || outByStep) continue;
15728
+ if (!scale.strict && !slots[i2].isStrong && slots[i2].value <= shortSteps)
15729
+ continue;
15730
+ const up = clampSemi(walk(scale, semi, 1), MELODY_LOW, MELODY_HIGH);
15731
+ const down = clampSemi(walk(scale, semi, -1), MELODY_LOW, MELODY_HIGH);
15732
+ const score = (s) => (isOutsideCore(scale, s) ? -4 : 0) + toneWeight(s, tones) + (s === before ? -3 : 0) + (after !== null ? -Math.abs(after - s) / 12 : 0);
14983
15733
  out[i2] = score(down) >= score(up) ? down : up;
14984
15734
  }
14985
15735
  };
14986
- var DIATONIC_PCS = new Set(MAJOR_SCALE2.map((d) => d.semi));
14987
15736
  var SHARP_FIFTHS = [0, 7, 2, 9, 4, -1, 6, 1, 8, 3, 10, 5];
14988
15737
  var FLAT_FIFTHS = [0, -5, 2, -3, 4, -1, -6, 1, -4, 3, -2, 5];
14989
- var diatonicFifth = (semi) => degreeToPitch(semitoneToDegree(semi)).fifth;
14990
15738
  var nearestOctaveOf = (near, semi) => {
14991
15739
  const pc = pitchClass(semi);
14992
15740
  let best = pc;
@@ -15001,7 +15749,7 @@ var nearestOctaveOf = (near, semi) => {
15001
15749
  }
15002
15750
  return best;
15003
15751
  };
15004
- var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15752
+ var applyChromatic = (scale, pitches, fifths, slots, tones, opts) => {
15005
15753
  const last = pitches.length - 1;
15006
15754
  for (let i2 = 0; i2 < pitches.length; i2++) {
15007
15755
  const tone = tones.find(
@@ -15009,7 +15757,8 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15009
15757
  );
15010
15758
  if (tone) fifths[i2] = tone.fifth;
15011
15759
  }
15012
- const altered = tones.filter((t) => !DIATONIC_PCS.has(pitchClass(t.semi)));
15760
+ const pcs = scalePcs(scale);
15761
+ const altered = tones.filter((t) => !pcs.has(pitchClass(t.semi)));
15013
15762
  for (let i2 = 0; i2 < pitches.length; i2++) {
15014
15763
  if (opts.keepLast && i2 === last) continue;
15015
15764
  if (!slots[i2].isStrong && slots[i2].value < opts.quarterSteps) continue;
@@ -15037,7 +15786,7 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15037
15786
  const span = Math.abs(after - before);
15038
15787
  const target = span === 2 ? before + dir : span === 3 ? after - dir : null;
15039
15788
  if (target === null) continue;
15040
- if (DIATONIC_PCS.has(pitchClass(target))) continue;
15789
+ if (pcs.has(pitchClass(target))) continue;
15041
15790
  if (target === before || target === after) continue;
15042
15791
  if (target < MELODY_LOW || target > MELODY_HIGH) continue;
15043
15792
  pitches[i2] = target;
@@ -15045,7 +15794,8 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15045
15794
  lastAltered = i2;
15046
15795
  }
15047
15796
  };
15048
- var draw = (options, resolvedKey, rnd) => {
15797
+ var draw = (options, resolvedKey, scale, rnd) => {
15798
+ const center = resolveCenter(scale);
15049
15799
  const stepsPerBar = options.stepsPerBar;
15050
15800
  const edo = options.edo === 31 ? 31 : 12;
15051
15801
  const scaleStep = (v) => Math.max(1, Math.round(Math.abs(v) * stepsPerBar / BASE_STEPS_PER_BAR)) * Math.sign(v);
@@ -15073,7 +15823,7 @@ var draw = (options, resolvedKey, rnd) => {
15073
15823
  const totalBars = sectionPlan.reduce((sum, s) => sum + s.bars, 0);
15074
15824
  const relativeKinds = /* @__PURE__ */ new Set();
15075
15825
  let relativeShift = 0;
15076
- if (rnd() < 0.25) {
15826
+ if (rnd() < 0.25 && !center) {
15077
15827
  for (const kind of pick(
15078
15828
  [["prechorus"], ["bridge"], ["prechorus", "bridge"]],
15079
15829
  rnd
@@ -15183,42 +15933,42 @@ var draw = (options, resolvedKey, rnd) => {
15183
15933
  barKeyShift[b] = s.keyShift;
15184
15934
  }
15185
15935
  }
15186
- const progAPool = resolvedKey.mode === "major" ? SECTION_A_PROGRESSIONS.filter((p) => !p[0].startsWith("Am")) : resolvedKey.mode === "minor" ? SECTION_A_PROGRESSIONS.filter((p) => p[0].startsWith("Am")) : SECTION_A_PROGRESSIONS;
15936
+ const progAPool = center ? center.a : resolvedKey.mode === "major" ? SECTION_A_PROGRESSIONS.filter((p) => !p[0].startsWith("Am")) : resolvedKey.mode === "minor" ? SECTION_A_PROGRESSIONS.filter((p) => p[0].startsWith("Am")) : SECTION_A_PROGRESSIONS;
15187
15937
  const withoutTonic = (pool, root) => {
15188
- const isTonic = (c) => root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
15938
+ const isTonic = (c) => center ? center.tonicPattern.test(c) : root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
15189
15939
  const out = pool.filter((p) => !p.some(isTonic));
15190
15940
  return out.length > 0 ? out : pool;
15191
15941
  };
15192
- const homeRoot = resolvedKey.mode === "minor" ? "Am" : "C";
15942
+ const homeRoot = center ? center.tonic : resolvedKey.mode === "minor" ? "Am" : "C";
15193
15943
  const progA = pick(
15194
15944
  floating ? withoutTonic(progAPool, homeRoot) : progAPool,
15195
15945
  rnd
15196
15946
  );
15197
- const progBPool = SECTION_B_PROGRESSIONS.filter(
15947
+ const progBPool = (center ? center.b : SECTION_B_PROGRESSIONS).filter(
15198
15948
  (p) => p.join("|") !== progA.join("|")
15199
15949
  );
15200
15950
  const progB = pick(
15201
15951
  floating ? withoutTonic(progBPool, homeRoot) : progBPool,
15202
15952
  rnd
15203
15953
  );
15204
- const progRelativePool = SECTION_A_PROGRESSIONS.filter(
15954
+ const progRelativePool = center ? center.a : SECTION_A_PROGRESSIONS.filter(
15205
15955
  (p) => resolvedKey.mode === "minor" ? !p[0].startsWith("Am") : p[0].startsWith("Am")
15206
15956
  );
15207
15957
  const progRelative = pick(
15208
15958
  floating ? withoutTonic(progRelativePool, homeRoot) : progRelativePool,
15209
15959
  rnd
15210
15960
  );
15211
- const progCPool = SECTION_C_PROGRESSIONS.filter(
15961
+ const progCPool = (center ? center.c : SECTION_C_PROGRESSIONS).filter(
15212
15962
  (p) => p.join("|") !== progA.join("|") && p.join("|") !== progB.join("|")
15213
15963
  );
15214
15964
  const progC = pick(
15215
15965
  floating ? withoutTonic(progCPool, homeRoot) : progCPool,
15216
15966
  rnd
15217
15967
  );
15218
- const tonic = progA[0].startsWith("Am") ? "Am" : "C";
15219
- const progHalf = pick(SECTION_A2_DERIVATIONS, rnd)(progA);
15220
- const progFull = pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
15221
- const progDeceptive = pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
15968
+ const tonic = center ? center.tonic : progA[0].startsWith("Am") ? "Am" : "C";
15969
+ const progHalf = center ? pick(MODAL_HALF_DERIVATIONS, rnd)(progA, center) : pick(SECTION_A2_DERIVATIONS, rnd)(progA);
15970
+ const progFull = center ? pick(MODAL_FULL_DERIVATIONS, rnd)(progA, center) : pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
15971
+ const progDeceptive = center ? pick(MODAL_DECEPTIVE_DERIVATIONS, rnd)(progA, center) : pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
15222
15972
  let lastChorusBar = -1;
15223
15973
  for (const section of sectionPlan)
15224
15974
  if (section.kind === "chorus" || section.kind === "outro")
@@ -15265,7 +16015,7 @@ var draw = (options, resolvedKey, rnd) => {
15265
16015
  progression[bar - 1] = transposeChordName("G7", shift - prev);
15266
16016
  }
15267
16017
  }
15268
- if (rnd() < 0.3) {
16018
+ if (rnd() < 0.3 && !center) {
15269
16019
  const tonicNames = tonic === "Am" ? ["Am", "Am7"] : ["C", "CM7"];
15270
16020
  const spots = [];
15271
16021
  for (let bar = 0; bar + 1 < totalBars; bar++) {
@@ -15286,21 +16036,27 @@ var draw = (options, resolvedKey, rnd) => {
15286
16036
  const sourceOf = (kind) => kind === "chorus" || kind === "interlude" || kind === "drop_chorus" ? "b" : kind === "prechorus" ? "a2" : kind === "bridge" ? "c" : "a";
15287
16037
  const landingOf = (section) => {
15288
16038
  if (section.spec.landing === null) return null;
15289
- const minorHere = resolvedKey.mode === "minor" !== relativeKinds.has(section.kind);
15290
- let landing = (section.spec.landing + (minorHere ? 5 : 0)) % 7;
15291
- if (floating && landing === (minorHere ? 5 : 0))
15292
- landing = (landing + pick([2, 4], rnd)) % 7;
16039
+ const relativeHere = relativeKinds.has(section.kind);
16040
+ const tonicDegree = relativeHere ? scale.tonic === 5 ? 0 : 5 : scale.tonic;
16041
+ const size = scaleSize(scale);
16042
+ let landing = (section.spec.landing + tonicDegree) % size;
16043
+ if (floating && landing === tonicDegree)
16044
+ landing = (landing + pick([2, 4], rnd)) % size;
15293
16045
  return landing;
15294
16046
  };
16047
+ const form = resolveMelodyForm(options.form, rnd);
15295
16048
  const units2 = [];
15296
16049
  for (const section of sectionPlan) {
15297
16050
  const unitCount = Math.max(1, Math.round(section.bars / 2));
15298
16051
  const src = sourceOf(section.kind);
15299
16052
  for (let u = 0; u < unitCount; u++) {
15300
16053
  if (!section.spec.melody) {
16054
+ const solo2 = section.kind === "interlude";
15301
16055
  units2.push({
15302
- role: "hold",
15303
- source: "silent",
16056
+ // 見せ場なので走句と山を交互に置く。`hold` のままだと
16057
+ // リズム型が最も薄いものになり、ソロにならない。
16058
+ role: solo2 ? u % 2 === 0 ? "run" : "climax" : "hold",
16059
+ source: solo2 ? "solo" : "silent",
15304
16060
  landing: null,
15305
16061
  section
15306
16062
  });
@@ -15309,16 +16065,31 @@ var draw = (options, resolvedKey, rnd) => {
15309
16065
  const isLast = u === unitCount - 1;
15310
16066
  if (u % 2 === 0) {
15311
16067
  units2.push({
15312
- role: section.kind === "bridge" ? "step" : section.kind === "chorus" ? "climax" : src === "a2" || u > 0 ? "sequence" : "motif",
15313
- source: src,
16068
+ // リフ主体の曲は**変形しない**。セクエンツもオクターブ上げも入れず、
16069
+ // 同じ型を回し続ける。セクションの対比は編曲側(ドラム・楽器・レイヤ)
16070
+ // が担う——ヤツメ穴型の曲がまさにその作りで、120小節を通して
16071
+ // 5半音のセルが変わらない。
16072
+ role: form === "ostinato" ? "motif" : form === "through" ? (
16073
+ // 通し作曲は素材を戻さない。`step` の書法(アーチ・谷・波)で
16074
+ // 独立した線を書き、たまに走句と山を挟んで単調さを避ける。
16075
+ u % 3 === 2 ? "run" : u % 3 === 1 ? "climax" : "step"
16076
+ ) : section.kind === "bridge" ? "step" : section.kind === "chorus" ? "climax" : src === "a2" || u > 0 ? "sequence" : "motif",
16077
+ // リフ型は素材も1つに揃える(`sourceOf` でセクションごとに
16078
+ // 変えると、そこだけ別の型が始まってオスティナートにならない)。
16079
+ source: form === "ostinato" ? "a" : src,
15314
16080
  landing: null,
15315
16081
  section
15316
16082
  });
15317
16083
  } else {
15318
16084
  const landing = landingOf(section);
16085
+ const riff = form === "ostinato" && !(isLast && landing === 0);
15319
16086
  units2.push({
15320
- role: isLast && landing === 0 ? "cadence" : "answer",
15321
- source: "answer",
16087
+ role: isLast && landing === 0 ? "cadence" : riff ? "motif" : (
16088
+ // 通し作曲は「問いと答え」で閉じない。answer は問いのリズムを
16089
+ // 受けて着地音だけ変える形なので、そのままだと反復が戻る。
16090
+ form === "through" ? "step" : "answer"
16091
+ ),
16092
+ source: riff ? "a" : "answer",
15322
16093
  landing: isLast ? landing : null,
15323
16094
  section
15324
16095
  });
@@ -15336,6 +16107,7 @@ var draw = (options, resolvedKey, rnd) => {
15336
16107
  const restatementOf = (bar) => {
15337
16108
  const curSec = sectionAt(sectionPlan, bar);
15338
16109
  if (!curSec.spec.melody) return null;
16110
+ if (form === "through") return null;
15339
16111
  if (curSec.restatement) {
15340
16112
  const firstSec = sectionPlan.find(
15341
16113
  (s) => s.kind === curSec.kind && !s.restatement
@@ -15357,14 +16129,19 @@ var draw = (options, resolvedKey, rnd) => {
15357
16129
  }
15358
16130
  return null;
15359
16131
  };
16132
+ const registerSpread = form === "ostinato" ? rnd() ** 2 * 0.5 : 0.25 + rnd() * 0.75;
15360
16133
  const style = {
15361
16134
  groove: pick(["eighth", "sixteenth"], rnd),
15362
16135
  arcPeriod: pick([4, 8, 8, 16], rnd),
15363
16136
  arcPhase: pick([0, 1, 2], rnd),
15364
- arcAmp: 2 + rnd() * 3,
16137
+ arcAmp: 5 * registerSpread,
15365
16138
  // オクターブ跳躍は参考曲では音程の1.0%しかない。上げすぎると音域が広がる。
15366
- octaveAffinity: 0.06 + rnd() * 0.12,
15367
- pentatonicMotif: rnd() < 0.55,
16139
+ octaveAffinity: 0.18 * registerSpread,
16140
+ maxLeap: pick(LEAP_CEILINGS, rnd),
16141
+ // **音階を厳しく締める曲は必ず中核音の歩数で組む。** ダイアトニックの度数で輪郭を
16142
+ // 作ると、琉球音階なのにレやラが輪郭の中に入り込む。ファ・シを自由に使う
16143
+ // 陽・民謡だけが、曲ごとに掛けたり掛けなかったりする({@link ComposeScale.strict})。
16144
+ pentatonicMotif: rnd() < 0.55 || scale.strict,
15368
16145
  runShape: pick(["scale", "turn", "broken", "zigzag"], rnd),
15369
16146
  stepShape: pick(
15370
16147
  ["arch", "valley", "ascend", "descend", "wave", "pivot"],
@@ -15382,7 +16159,11 @@ var draw = (options, resolvedKey, rnd) => {
15382
16159
  // 界隈曲らしさ:調の外の音(クロマチック)や微小な逸脱を積極的に許容する。
15383
16160
  // 刻みを細かくすると経過音の置き場所が増えるので、同じ係数でも変化音は増える。
15384
16161
  // 参考曲の1.75倍まで伸びていたぶんを引く。
15385
- chromaticAffinity: rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33,
16162
+ // **音階を厳しく締める曲は変化音を控える。** 半音の経過音は長調・短調の泣きメロの
16163
+ // 芯だが、琉球・都節・律ではその半音が音階の外にしか無く、入れたぶんだけ
16164
+ // 音階の色が薄まる(実測で嬰ヘが3%出て、音階内のラ2%より多いという逆転が
16165
+ // 起きていた)。0 にはしない——民族音階の実際の曲にも装飾の半音は出る。
16166
+ chromaticAffinity: (rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33) * (scale.strict ? 0.4 : 1),
15386
16167
  barHeadWeight: rnd() < 0.5 ? 3 : 2,
15387
16168
  bassStyle: pick(
15388
16169
  [
@@ -15409,8 +16190,11 @@ var draw = (options, resolvedKey, rnd) => {
15409
16190
  subInterval: pick([3, 4, 8, 9], rnd)
15410
16191
  };
15411
16192
  const motifPool = groovyCells(MOTIF_CELLS, style.groove, rnd);
15412
- const targetNotesPerBar = 4.5 + rnd() * 4.5;
15413
- const targetRestRatio = 0.02 + rnd() * 0.1;
16193
+ const targetRestRatio = 0.02 + rnd() ** 2 * 0.42;
16194
+ const targetNotesPerBar = Math.max(
16195
+ 2.8,
16196
+ Math.min(9.5, 7.4 - 6 * targetRestRatio + (rnd() * 4 - 2))
16197
+ );
15414
16198
  const cellNotes = (c) => c.value.filter((v) => v > 0).length;
15415
16199
  const cellRest = (c) => {
15416
16200
  let rest = 0;
@@ -15433,9 +16217,13 @@ var draw = (options, resolvedKey, rnd) => {
15433
16217
  return pool[pool.length - 1];
15434
16218
  };
15435
16219
  const pickCell = (pool, densityMul = 1) => {
15436
- let best = weightedPick(pool);
16220
+ const want = targetRestRatio / Math.max(0.5, densityMul);
16221
+ const tol = Math.max(0.08, want * 0.6);
16222
+ const near = pool.filter((c) => Math.abs(cellRest(c) - want) <= tol);
16223
+ const from = near.length > 0 ? near : pool;
16224
+ let best = weightedPick(from);
15437
16225
  for (let i2 = 0; i2 < 2; i2++) {
15438
- const c = weightedPick(pool);
16226
+ const c = weightedPick(from);
15439
16227
  if (cellDistance(c, densityMul) < cellDistance(best, densityMul))
15440
16228
  best = c;
15441
16229
  }
@@ -15537,7 +16325,7 @@ var draw = (options, resolvedKey, rnd) => {
15537
16325
  if (breathBars.has(source)) breathBars.add(bar);
15538
16326
  continue;
15539
16327
  }
15540
- const isB = units2[u].source === "b";
16328
+ const isB = units2[u].source === "b" || units2[u].source === "solo";
15541
16329
  const isA2 = units2[u].source === "a2";
15542
16330
  const isC = units2[u].source === "c";
15543
16331
  const isPrechorusEnd = units2[u].section.kind === "prechorus" && bar === units2[u].section.startBar + units2[u].section.bars - 1;
@@ -15587,6 +16375,7 @@ var draw = (options, resolvedKey, rnd) => {
15587
16375
  const harmony = [];
15588
16376
  const harmony2 = [];
15589
16377
  const pad = [];
16378
+ const solo = [];
15590
16379
  const melodyDurations = [];
15591
16380
  const barTension = new Array(totalBars).fill(0);
15592
16381
  let restSteps = 0;
@@ -15597,9 +16386,9 @@ var draw = (options, resolvedKey, rnd) => {
15597
16386
  let chromaticNotes = 0;
15598
16387
  let sungBars = 0;
15599
16388
  let prevSemi = pick([60, 64, 65, 67, 69, 72, 74, 76], rnd);
15600
- const pickFigure = (gapSteps, scale) => {
16389
+ const pickFigure = (gapSteps, scale2) => {
15601
16390
  const fits = ANSWER_FIGURES.filter(
15602
- (f) => f.reduce((sum, v) => sum + scale(Math.abs(v)), 0) <= gapSteps
16391
+ (f) => f.reduce((sum, v) => sum + scale2(Math.abs(v)), 0) <= gapSteps
15603
16392
  );
15604
16393
  return fits.length === 0 ? null : pick(fits, rnd);
15605
16394
  };
@@ -15657,8 +16446,9 @@ var draw = (options, resolvedKey, rnd) => {
15657
16446
  slots,
15658
16447
  tones,
15659
16448
  style,
16449
+ scale,
15660
16450
  motifContour,
15661
- semitoneToDegree(headSemi),
16451
+ semitoneToDegree(scale, headSemi),
15662
16452
  contourOffset,
15663
16453
  repeatShift,
15664
16454
  headWeight,
@@ -15674,7 +16464,8 @@ var draw = (options, resolvedKey, rnd) => {
15674
16464
  ...plannedDegrees[source]
15675
16465
  );
15676
16466
  const landing = units2[unitOf(bar)].landing;
15677
- if (landing !== null && barInUnit(bar) === 1) landOn(degrees, landing);
16467
+ if (landing !== null && barInUnit(bar) === 1)
16468
+ landOn(scale, degrees, landing);
15678
16469
  plannedDegrees[bar] = [...degrees];
15679
16470
  const isMotifBar = source !== null || role === "motif" || role === "sequence" || role === "climax" || role === "answer";
15680
16471
  let fitted = degrees;
@@ -15686,13 +16477,18 @@ var draw = (options, resolvedKey, rnd) => {
15686
16477
  tones,
15687
16478
  prevSemi,
15688
16479
  quarterSteps,
16480
+ scale,
15689
16481
  style.pentatonicMotif,
15690
- motifShiftMemo.get(shiftKey) ?? null
16482
+ motifShiftMemo.get(shiftKey) ?? null,
16483
+ // リフ型は和音へ寄せない。同じセルを回し続けるのが役目。
16484
+ form === "ostinato" ? 0 : 3
15691
16485
  );
15692
16486
  fitted = r.degrees;
15693
16487
  motifShiftMemo.set(shiftKey, r.shift);
15694
16488
  }
15695
16489
  const pitches = shapeBar(fitted, slots, tones, prevSemi, {
16490
+ scale,
16491
+ maxLeap: style.maxLeap,
15696
16492
  allowLeap: role === "climax",
15697
16493
  allowArpeggio: role === "climax" || role === "run" && style.runShape === "broken" || role === "cadence" && style.cadenceShape !== "descend",
15698
16494
  quarterSteps,
@@ -15707,16 +16503,19 @@ var draw = (options, resolvedKey, rnd) => {
15707
16503
  rnd,
15708
16504
  preserveContour: isMotifBar
15709
16505
  });
15710
- if (landing !== null && barInUnit(bar) === 1) landPitch(pitches, landing);
15711
- const fifths = pitches.map(diatonicFifth);
15712
- applyChromatic(pitches, fifths, slots, tones, {
16506
+ if (landing !== null && barInUnit(bar) === 1)
16507
+ landPitch(scale, pitches, landing);
16508
+ const fifths = pitches.map((semi) => scaleFifth(scale, semi));
16509
+ applyChromatic(scale, pitches, fifths, slots, tones, {
15713
16510
  affinity: style.chromaticAffinity,
15714
16511
  quarterSteps,
15715
16512
  shortSteps: scaleStep(EIGHTH),
15716
16513
  keepLast: landing !== null && barInUnit(bar) === 1,
15717
16514
  rnd
15718
16515
  });
15719
- const silent = units2[unitOf(bar)].source === "silent";
16516
+ const unitSource = units2[unitOf(bar)].source;
16517
+ const isSolo = unitSource === "solo";
16518
+ const silent = unitSource === "silent" || isSolo;
15720
16519
  const barHead = pitches[0];
15721
16520
  let tensionSum = 0;
15722
16521
  let tensionSteps = 0;
@@ -15724,7 +16523,7 @@ var draw = (options, resolvedKey, rnd) => {
15724
16523
  const semi = pitches[i2];
15725
16524
  tensionSum += (3 - toneWeight(semi, tones)) / 3 * slots[i2].value;
15726
16525
  tensionSteps += slots[i2].value;
15727
- if (role !== "climax") {
16526
+ if (!silent && role !== "climax") {
15728
16527
  const gap = Math.abs(semi - prevSemi);
15729
16528
  maxLeap = Math.max(maxLeap, gap);
15730
16529
  intervals++;
@@ -15733,10 +16532,25 @@ var draw = (options, resolvedKey, rnd) => {
15733
16532
  }
15734
16533
  const slot = slots[i2];
15735
16534
  if (silent) {
16535
+ if (isSolo) {
16536
+ const ks = barKeyShift[bar];
16537
+ const fifthShiftSolo = ks === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(ks % 12 + 12) % 12];
16538
+ solo.push({
16539
+ startStep: barStart + slot.at,
16540
+ pitchUnits: spelledToUnits(
16541
+ semi + ks,
16542
+ fifths[i2] + fifthShiftSolo,
16543
+ edo
16544
+ ),
16545
+ durationSteps: slot.value,
16546
+ // ソロは前に出る声部なので、歌メロより気持ち強く弾く。
16547
+ velocity: slot.at === 0 ? 116 : slot.value <= scaleStep(SIXTEENTH) ? 96 : 106
16548
+ });
16549
+ }
15736
16550
  prevSemi = semi;
15737
16551
  continue;
15738
16552
  }
15739
- if (!DIATONIC_PCS.has(pitchClass(semi))) chromaticNotes++;
16553
+ if (!scalePcs(scale).has(pitchClass(semi))) chromaticNotes++;
15740
16554
  const k = barKeyShift[bar];
15741
16555
  const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15742
16556
  melody.push({
@@ -15790,7 +16604,7 @@ var draw = (options, resolvedKey, rnd) => {
15790
16604
  subSemi = pushSub(
15791
16605
  cursor,
15792
16606
  step,
15793
- i2 === 0 ? subSemi : walk(subSemi, subDir)
16607
+ i2 === 0 ? subSemi : walk(scale, subSemi, subDir)
15794
16608
  );
15795
16609
  placed++;
15796
16610
  }
@@ -15814,7 +16628,7 @@ var draw = (options, resolvedKey, rnd) => {
15814
16628
  subSemi = pushSub(
15815
16629
  cursor,
15816
16630
  step,
15817
- index === 0 ? subSemi : walk(subSemi, subDir * (index % 2 === 0 ? 1 : -1))
16631
+ index === 0 ? subSemi : walk(scale, subSemi, subDir * (index % 2 === 0 ? 1 : -1))
15818
16632
  );
15819
16633
  index++;
15820
16634
  }
@@ -15824,7 +16638,11 @@ var draw = (options, resolvedKey, rnd) => {
15824
16638
  pushSub(0, stepsPerBar, subSemi);
15825
16639
  } else if (subStyle === "long-short") {
15826
16640
  subSemi = pushSub(0, scaleStep(DOT_HALF), subSemi);
15827
- pushSub(scaleStep(DOT_HALF), scaleStep(QUARTER), walk(subSemi, subDir));
16641
+ pushSub(
16642
+ scaleStep(DOT_HALF),
16643
+ scaleStep(QUARTER),
16644
+ walk(scale, subSemi, subDir)
16645
+ );
15828
16646
  } else if (role === "cadence") {
15829
16647
  pushSub(0, stepsPerBar, subSemi);
15830
16648
  } else {
@@ -15839,6 +16657,7 @@ var draw = (options, resolvedKey, rnd) => {
15839
16657
  const written = [];
15840
16658
  for (let i2 = 0; i2 < slots.length; i2++) {
15841
16659
  const hTone = harmonyPitch(
16660
+ scale,
15842
16661
  pitches[i2],
15843
16662
  tones,
15844
16663
  last,
@@ -15909,6 +16728,7 @@ var draw = (options, resolvedKey, rnd) => {
15909
16728
  const nextTones = chordTones(progression[(bar + 1) % totalBars]);
15910
16729
  const A = clampSemi(
15911
16730
  walk(
16731
+ scale,
15912
16732
  nextTones[0] ? clampSemi(nextTones[0].semi, BASS_LOW, BASS_HIGH) : R,
15913
16733
  -1
15914
16734
  ),
@@ -15965,7 +16785,7 @@ var draw = (options, resolvedKey, rnd) => {
15965
16785
  let bassCursor = 0;
15966
16786
  for (const [semi, value] of bassCell) {
15967
16787
  const len = scaleStep(value);
15968
- const fifth = semi === R || semi === O ? rootTone.fifth : semi === F ? fifthTone?.fifth ?? rootTone.fifth : semi === T ? thirdTone?.fifth ?? rootTone.fifth : degreeToPitch(semitoneToDegree(semi)).fifth;
16788
+ const fifth = semi === R || semi === O ? rootTone.fifth : semi === F ? fifthTone?.fifth ?? rootTone.fifth : semi === T ? thirdTone?.fifth ?? rootTone.fifth : scaleFifth(scale, semi);
15969
16789
  const k = barKeyShift[bar];
15970
16790
  const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15971
16791
  barBass.push({
@@ -16027,7 +16847,7 @@ var draw = (options, resolvedKey, rnd) => {
16027
16847
  const after = melody[i2 + 2];
16028
16848
  if (after && Math.floor(after.startStep / stepsPerBar) === barIdx) {
16029
16849
  const afterSemi = Math.round(after.pitchUnits / UNITS_PER_SEMITONE) - (barKeyShift[barIdx] ?? 0);
16030
- if (!DIATONIC_PCS.has(pitchClass(afterSemi))) continue;
16850
+ if (!scalePcs(scale).has(pitchClass(afterSemi))) continue;
16031
16851
  }
16032
16852
  const nextRole = barRoles[barIdx];
16033
16853
  let tieProb = 0.3;
@@ -16048,11 +16868,13 @@ var draw = (options, resolvedKey, rnd) => {
16048
16868
  melodyDurations[i2] += melodyDurations[i2 + 1];
16049
16869
  melodyDurations.splice(i2 + 1, 1);
16050
16870
  }
16051
- const hCurIdx = harmony.findIndex((h) => h.startStep === cur.startStep);
16052
- const hNxtIdx = harmony.findIndex((h) => h.startStep === nxt.startStep);
16053
- if (hCurIdx >= 0 && hNxtIdx >= 0) {
16054
- harmony[hCurIdx].durationSteps += harmony[hNxtIdx].durationSteps;
16055
- harmony.splice(hNxtIdx, 1);
16871
+ for (const voice of [harmony, harmony2]) {
16872
+ const nxtIdx = voice.findIndex((h) => h.startStep === nxt.startStep);
16873
+ if (nxtIdx < 0) continue;
16874
+ const curIdx = voice.findIndex((h) => h.startStep === cur.startStep);
16875
+ if (curIdx >= 0)
16876
+ voice[curIdx].durationSteps += voice[nxtIdx].durationSteps;
16877
+ voice.splice(nxtIdx, 1);
16056
16878
  }
16057
16879
  i2--;
16058
16880
  }
@@ -16106,17 +16928,20 @@ var draw = (options, resolvedKey, rnd) => {
16106
16928
  };
16107
16929
  const shiftUnits = semitonesToUnits(rootShift, edo);
16108
16930
  if (shiftUnits !== 0)
16109
- for (const list of [melody, submelody, bass, harmony, harmony2, pad])
16931
+ for (const list of [melody, submelody, bass, harmony, harmony2, pad, solo])
16110
16932
  for (const n of list) n.pitchUnits = n.pitchUnits + shiftUnits;
16111
16933
  const octave = useOctaveLayer ? melody.filter(
16112
16934
  (n) => n.durationSteps >= quarterSteps || rnd() < octaveCoverage
16113
16935
  ).map((n) => ({ ...n, velocity: Math.max(40, n.velocity - 26) })) : [];
16114
16936
  return {
16937
+ form,
16115
16938
  chordProgression,
16116
16939
  chordPattern,
16117
16940
  rootShift,
16118
16941
  keyName: resolvedKey.keyName,
16119
16942
  keyLabel: resolvedKey.keyLabel,
16943
+ scaleId: scale.id,
16944
+ scaleLabel: scale.label,
16120
16945
  moodLabel: resolvedKey.moodLabel,
16121
16946
  bpm,
16122
16947
  sections: sectionPlan,
@@ -16136,6 +16961,7 @@ var draw = (options, resolvedKey, rnd) => {
16136
16961
  harmony2,
16137
16962
  octave,
16138
16963
  pad,
16964
+ solo,
16139
16965
  melodyDurations,
16140
16966
  restSteps,
16141
16967
  totalSteps: Math.max(1, sungBars) * stepsPerBar,
@@ -16195,7 +17021,7 @@ var evaluate = (d, recent) => {
16195
17021
  Math.min(...recent.map((r) => featureDistance(fingerprint, r))) / 1
16196
17022
  );
16197
17023
  const at = (b, v) => band(v, b[0], b[1], b[2], b[3]);
16198
- const atc = (key, v) => centeredBand(v, CORPUS_BANDS[key], CORPUS_MEDIANS[key]);
17024
+ const atc = (key, v) => plausibleBand(v, CORPUS_BANDS[key]);
16199
17025
  const peakBand = d.bars > 24 ? [0, 1, Math.round(d.bars / 16), Math.round(d.bars / 8) + 2] : HAND_BANDS.climaxPeaks;
16200
17026
  const scoreBreakdown = {
16201
17027
  entropy: atc("entropy", entropy),
@@ -16231,9 +17057,16 @@ var evaluate = (d, recent) => {
16231
17057
  tensionResolve: d.tonal.floating ? 1 : tension.resolve,
16232
17058
  novelty
16233
17059
  };
17060
+ const forgiven = new Set(
17061
+ Object.entries(WEIGHTS).filter(([key]) => BUDGETED_KEYS.has(key)).map(([key, weight]) => ({
17062
+ key,
17063
+ deficit: weight * (1 - (scoreBreakdown[key] ?? 0))
17064
+ })).sort((a, b) => b.deficit - a.deficit).slice(0, DEVIATION_BUDGET).filter((e) => e.deficit > 0).map((e) => e.key)
17065
+ );
16234
17066
  let weighted = 0;
16235
17067
  let weightSum = 0;
16236
17068
  for (const [key, weight] of Object.entries(WEIGHTS)) {
17069
+ if (forgiven.has(key)) continue;
16237
17070
  weighted += (scoreBreakdown[key] ?? 0) * weight;
16238
17071
  weightSum += weight;
16239
17072
  }
@@ -16265,49 +17098,74 @@ var composeSong = (options) => {
16265
17098
  const recent = options.recent ?? [];
16266
17099
  const count = Math.max(1, options.drawCount ?? DRAW_COUNT);
16267
17100
  const resolvedKey = resolveComposeKey(options.baseKey, rnd);
16268
- let best = null;
16269
- let bestScore = Number.NEGATIVE_INFINITY;
16270
- let bestIsValid = false;
17101
+ const scale = resolveComposeScale(
17102
+ options.scale,
17103
+ resolvedKey.mode === "minor",
17104
+ rnd
17105
+ );
17106
+ const valid = [];
17107
+ const invalid = [];
16271
17108
  let rejected = 0;
16272
17109
  for (let attempt = 1; attempt <= count; attempt++) {
16273
- const d = draw(options, resolvedKey, rnd);
16274
- const { stats, ok } = evaluate(d, recent);
16275
- if (!ok) rejected++;
16276
- const better = ok === bestIsValid ? stats.score > bestScore : ok;
16277
- if (!better) continue;
16278
- bestScore = stats.score;
16279
- bestIsValid = ok;
16280
- best = {
16281
- // ドラム・楽器は勝った候補にだけ後から付ける(メロディに依存しないので
16282
- // 候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
16283
- drum: "",
16284
- instrument: "",
16285
- chordProgression: d.chordProgression,
16286
- chordPattern: d.chordPattern,
16287
- rootShift: d.rootShift,
16288
- keyName: d.keyName,
16289
- keyLabel: d.keyLabel,
16290
- moodLabel: d.moodLabel,
16291
- bpm: d.bpm,
16292
- sections: d.sections,
16293
- bars: d.bars,
16294
- vocal: d.vocal,
16295
- tonal: d.tonal,
16296
- melody: d.melody,
16297
- submelody: d.submelody,
16298
- bass: d.bass,
16299
- harmony: d.harmony,
16300
- harmony2: d.harmony2,
16301
- octave: d.octave,
16302
- pad: d.pad,
16303
- stats: { ...stats, attempts: attempt, rejected }
16304
- };
17110
+ const d2 = draw(options, resolvedKey, scale, rnd);
17111
+ const { stats, ok } = evaluate(d2, recent);
17112
+ if (ok) valid.push({ d: d2, stats });
17113
+ else {
17114
+ rejected++;
17115
+ invalid.push({ d: d2, stats });
17116
+ }
17117
+ }
17118
+ const pool = valid.length > 0 ? valid : invalid;
17119
+ const top = Math.max(...pool.map((c) => c.stats.score));
17120
+ const weights = pool.map(
17121
+ (c) => Math.exp((c.stats.score - top) / SELECT_TEMPERATURE)
17122
+ );
17123
+ const total = weights.reduce((a, b) => a + b, 0);
17124
+ let ticket = rnd() * total;
17125
+ let chosen = pool[pool.length - 1];
17126
+ for (let i2 = 0; i2 < pool.length; i2++) {
17127
+ ticket -= weights[i2];
17128
+ if (ticket <= 0) {
17129
+ chosen = pool[i2];
17130
+ break;
17131
+ }
16305
17132
  }
16306
- const result = best;
17133
+ const d = chosen.d;
17134
+ const result = {
17135
+ // ドラム・楽器・編曲プランは勝った候補にだけ後から付ける(メロディに
17136
+ // 依存しないので候補ごとに引いても採点は動かず、候補数ぶん無駄になる)。
17137
+ drum: "",
17138
+ instrument: "",
17139
+ arrange: EMPTY_ARRANGE,
17140
+ chordProgression: d.chordProgression,
17141
+ chordPattern: d.chordPattern,
17142
+ rootShift: d.rootShift,
17143
+ keyName: d.keyName,
17144
+ keyLabel: d.keyLabel,
17145
+ scaleId: scale.id,
17146
+ scaleLabel: scale.label,
17147
+ form: d.form,
17148
+ moodLabel: d.moodLabel,
17149
+ bpm: d.bpm,
17150
+ sections: d.sections,
17151
+ bars: d.bars,
17152
+ vocal: d.vocal,
17153
+ tonal: d.tonal,
17154
+ melody: d.melody,
17155
+ submelody: d.submelody,
17156
+ bass: d.bass,
17157
+ harmony: d.harmony,
17158
+ harmony2: d.harmony2,
17159
+ octave: d.octave,
17160
+ pad: d.pad,
17161
+ solo: d.solo,
17162
+ stats: { ...chosen.stats, attempts: count, rejected }
17163
+ };
16307
17164
  result.stats.attempts = count;
16308
17165
  result.stats.rejected = rejected;
16309
17166
  result.drum = pickBuiltinDrum(result, rnd);
16310
17167
  result.instrument = pickBuiltinInstrument(result, rnd);
17168
+ result.arrange = buildArrangePlan(result, rnd);
16311
17169
  return result;
16312
17170
  };
16313
17171
  var pickBuiltinDrum = (song, rnd) => {
@@ -16319,6 +17177,80 @@ var pickBuiltinDrum = (song, rnd) => {
16319
17177
  const pool = dotted >= 0.08 ? ["shuffle", "8beat", "16beat"] : song.bpm >= 150 ? ["4beat", "dance", "16beat", "disco"] : short >= 0.85 ? ["16beat", "dance", "disco"] : song.bpm <= 115 ? ["bossa", "8beat", "shuffle", "4beat"] : ["8beat", "4beat", "16beat", "dance"];
16320
17178
  return pick(pool, rnd);
16321
17179
  };
17180
+ var EMPTY_ARRANGE = {
17181
+ backing: [],
17182
+ sparkle: null,
17183
+ padSections: [],
17184
+ lead: null,
17185
+ bassLayer: null
17186
+ };
17187
+ var LOUD_KINDS = ["prechorus", "chorus", "bridge"];
17188
+ var QUIET_KINDS = [
17189
+ "intro",
17190
+ "verse",
17191
+ "interlude",
17192
+ "drop_chorus",
17193
+ "outro"
17194
+ ];
17195
+ var CHORUS_KINDS = ["chorus", "drop_chorus"];
17196
+ var buildArrangePlan = (song, rnd) => {
17197
+ const present = new Set(song.sections.map((s) => s.kind));
17198
+ const narrow = (kinds) => {
17199
+ const hit = kinds.filter((k) => present.has(k));
17200
+ return hit.length === 0 ? null : hit;
17201
+ };
17202
+ const base = {
17203
+ pattern: song.chordPattern,
17204
+ sections: null,
17205
+ octave: 0
17206
+ };
17207
+ const others = chordPatternPool(song.bpm).filter((p) => p !== base.pattern);
17208
+ const backing = [base];
17209
+ const addKinds = pick(
17210
+ [LOUD_KINDS, CHORUS_KINDS, LOUD_KINDS, QUIET_KINDS],
17211
+ rnd
17212
+ );
17213
+ const second = pick(others, rnd);
17214
+ backing.push({
17215
+ pattern: second,
17216
+ sections: narrow(addKinds),
17217
+ octave: 0
17218
+ });
17219
+ if (rnd() < 0.55) {
17220
+ const rest = others.filter((p) => p !== second);
17221
+ backing.push({
17222
+ pattern: pick(rest.length > 0 ? rest : others, rnd),
17223
+ // 2本目と逆側へ置く(両方サビに寄せると、サビだけ団子になる)。
17224
+ sections: narrow(addKinds === QUIET_KINDS ? LOUD_KINDS : QUIET_KINDS),
17225
+ octave: 0
17226
+ });
17227
+ }
17228
+ const sparklePool = chordPatternPool(song.bpm).filter(
17229
+ (p) => p !== "block" && !backing.some((b) => b.pattern === p)
17230
+ );
17231
+ const sparkle = sparklePool.length > 0 && rnd() < 0.7 ? {
17232
+ pattern: pick(sparklePool, rnd),
17233
+ sections: narrow(pick([LOUD_KINDS, CHORUS_KINDS], rnd)) ?? CHORUS_KINDS,
17234
+ octave: 1
17235
+ } : null;
17236
+ const padSections = narrow(
17237
+ pick(
17238
+ [
17239
+ ["prechorus", "chorus", "bridge", "drop_chorus"],
17240
+ ["chorus", "drop_chorus"],
17241
+ ["prechorus", "chorus", "bridge", "drop_chorus"],
17242
+ ["bridge", "chorus"]
17243
+ ],
17244
+ rnd
17245
+ )
17246
+ ) ?? [];
17247
+ const lead = rnd() < 0.75 ? {
17248
+ sections: narrow(pick([CHORUS_KINDS, LOUD_KINDS], rnd)) ?? CHORUS_KINDS,
17249
+ octave: rnd() < 0.5 ? 0 : 1
17250
+ } : null;
17251
+ const bassLayer = rnd() < 0.25 ? { sections: narrow(CHORUS_KINDS) ?? CHORUS_KINDS, octave: 1 } : null;
17252
+ return { backing, sparkle, padSections, lead, bassLayer };
17253
+ };
16322
17254
  var pickBuiltinInstrument = (song, rnd) => {
16323
17255
  const eighth = BASE_STEPS_PER_BAR / 8;
16324
17256
  const short = song.melody.filter((n) => n.durationSteps <= eighth).length / Math.max(1, song.melody.length);
@@ -16429,6 +17361,492 @@ var composeLyrics = (melody, options) => {
16429
17361
  return out.join("");
16430
17362
  };
16431
17363
 
17364
+ // src/instrument-presets.ts
17365
+ var INSTRUMENT_PRESETS = {
17366
+ // --- STANDARD: 汎用性と完成度重視 ---
17367
+ piano: {
17368
+ displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
17369
+ description: "\u6700\u3082\u7834\u7DBB\u3057\u306B\u304F\u3044\u69CB\u6210\u3002\u697D\u66F2\u5236\u4F5C\u306E\u30B9\u30B1\u30C3\u30C1\u306B\u3082\u6700\u9069\u3002",
17370
+ melody: "Acoustic Grand Piano",
17371
+ submelody: "Vibraphone",
17372
+ bass: "Electric Bass (finger)",
17373
+ chord: "Pad 2 (warm)",
17374
+ solo: "Electric Guitar (clean)",
17375
+ // **Glockenspiel は使わない。音源側のサンプルが音程を外している**(実聴で確認)。
17376
+ // 音域の問題(G5〜の高音楽器)なら {@link GM_BRIGHT_CEILING} で下げれば済むが、
17377
+ // ピッチそのものがずれているものは置き場所を変えても直らない。同じ役割
17378
+ // (サビの上に乗る明るい音板)で、素直に鳴る Celesta へ差し替えてある。
17379
+ // ※ {@link GM_INSTRUMENT_RANGE} / {@link GM_BRIGHT_CEILING} の Glockenspiel の
17380
+ // 項は残す。手で選んだときの置き場所の判断はそのまま要るため。
17381
+ chorusLead: "Celesta",
17382
+ chordAlt: "Electric Piano 1",
17383
+ sparkle: "Music Box",
17384
+ bassAlt: "Acoustic Bass",
17385
+ harmonyAlt: "Choir Aahs"
17386
+ },
17387
+ acoustic: {
17388
+ displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
17389
+ description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
17390
+ melody: "Acoustic Guitar (steel)",
17391
+ submelody: "Harmonica",
17392
+ bass: "Acoustic Bass",
17393
+ chord: "Acoustic Guitar (nylon)",
17394
+ solo: "Overdriven Guitar",
17395
+ chorusLead: "String Ensemble 1",
17396
+ chordAlt: "Acoustic Grand Piano",
17397
+ sparkle: "Celesta",
17398
+ bassAlt: "Electric Bass (finger)",
17399
+ harmonyAlt: "Choir Aahs"
17400
+ },
17401
+ jazz_night: {
17402
+ displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
17403
+ description: "Rhodes\u98A8\u306EEP\u3068\u30A6\u30C3\u30C9\u30D9\u30FC\u30B9\u306B\u3088\u308B\u3001\u5927\u4EBA\u3073\u305F\u30A2\u30F3\u30B5\u30F3\u30D6\u30EB\u3002",
17404
+ melody: "Electric Piano 1",
17405
+ submelody: "Flute",
17406
+ bass: "Acoustic Bass",
17407
+ chord: "Electric Guitar (jazz)",
17408
+ solo: "Tenor Sax",
17409
+ chorusLead: "Muted Trumpet",
17410
+ chordAlt: "Vibraphone",
17411
+ sparkle: "Celesta",
17412
+ bassAlt: "Electric Bass (finger)",
17413
+ harmonyAlt: "Choir Aahs"
17414
+ },
17415
+ // --- MODERN & VIBE: エッジの効いた現代的な響き ---
17416
+ synth_pop: {
17417
+ displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
17418
+ description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
17419
+ melody: "Lead 2 (sawtooth)",
17420
+ submelody: "Lead 4 (chiff)",
17421
+ bass: "Synth Bass 2",
17422
+ chord: "Pad 3 (polysynth)",
17423
+ solo: "Distortion Guitar",
17424
+ chorusLead: "Synth Brass 1",
17425
+ chordAlt: "Electric Piano 2",
17426
+ sparkle: "FX 3 (crystal)",
17427
+ bassAlt: "Synth Bass 1",
17428
+ harmonyAlt: "Synth Choir"
17429
+ },
17430
+ cyber_punk: {
17431
+ displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
17432
+ description: "\u30C7\u30B8\u30BF\u30EB\u306A\u51B7\u305F\u3055\u3068\u6B6A\u307F\u304C\u6DF7\u3056\u308A\u5408\u3046\u3001\u672A\u6765\u7684\u306A\u97FF\u304D\u3002",
17433
+ melody: "Lead 8 (bass + lead)",
17434
+ submelody: "Lead 5 (charang)",
17435
+ bass: "Synth Bass 2",
17436
+ chord: "Pad 8 (sweep)",
17437
+ solo: "Distortion Guitar",
17438
+ chorusLead: "Lead 7 (fifths)",
17439
+ chordAlt: "Pad 4 (choir)",
17440
+ sparkle: "FX 3 (crystal)",
17441
+ bassAlt: "Synth Bass 1",
17442
+ harmonyAlt: "Synth Choir"
17443
+ },
17444
+ rock: {
17445
+ displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
17446
+ description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
17447
+ melody: "Distortion Guitar",
17448
+ submelody: "Rock Organ",
17449
+ bass: "Electric Bass (pick)",
17450
+ chord: "Overdriven Guitar",
17451
+ solo: "Distortion Guitar",
17452
+ chorusLead: "Brass Section",
17453
+ chordAlt: "Electric Guitar (clean)",
17454
+ sparkle: "Electric Guitar (muted)",
17455
+ bassAlt: "Electric Bass (finger)",
17456
+ harmonyAlt: "Choir Aahs"
17457
+ },
17458
+ // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
17459
+ orchestra: {
17460
+ displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
17461
+ description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
17462
+ melody: "French Horn",
17463
+ submelody: "Pizzicato Strings",
17464
+ bass: "Cello",
17465
+ chord: "Tremolo Strings",
17466
+ solo: "Violin",
17467
+ chorusLead: "Trumpet",
17468
+ chordAlt: "String Ensemble 1",
17469
+ sparkle: "Orchestral Harp",
17470
+ bassAlt: "Contrabass",
17471
+ harmonyAlt: "Choir Aahs"
17472
+ },
17473
+ japanese_wa: {
17474
+ displayName: "\u548C\u98A8\u30FB\u96C5",
17475
+ description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
17476
+ melody: "Koto",
17477
+ submelody: "Shamisen",
17478
+ bass: "Taiko Drum",
17479
+ chord: "Shakuhachi",
17480
+ solo: "Shakuhachi",
17481
+ // piano プリセットと同じ理由で Glockenspiel を避ける(音源のピッチずれ)。
17482
+ chorusLead: "Celesta",
17483
+ chordAlt: "Kalimba",
17484
+ sparkle: "Music Box",
17485
+ bassAlt: "Acoustic Bass",
17486
+ harmonyAlt: "Choir Aahs"
17487
+ },
17488
+ arabic_exotic: {
17489
+ displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
17490
+ description: "\u30B7\u30BF\u30FC\u30EB\u3084\u30D0\u30B0\u30D1\u30A4\u30D7\u306B\u3088\u308B\u3001\u7570\u56FD\u60C5\u7DD2\u6EA2\u308C\u308B\u30B5\u30A6\u30F3\u30C9\u3002",
17491
+ melody: "Sitar",
17492
+ submelody: "Bagpipe",
17493
+ bass: "Fretless Bass",
17494
+ chord: "Kalimba",
17495
+ solo: "Shanai",
17496
+ chorusLead: "Steel Drums",
17497
+ chordAlt: "Orchestral Harp",
17498
+ sparkle: "Tinkle Bell",
17499
+ bassAlt: "Acoustic Bass",
17500
+ harmonyAlt: "Choir Aahs"
17501
+ },
17502
+ // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
17503
+ fantasy_rpg: {
17504
+ displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
17505
+ description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
17506
+ melody: "Ocarina",
17507
+ submelody: "Celesta",
17508
+ bass: "Timpani",
17509
+ chord: "Orchestral Harp",
17510
+ solo: "Pan Flute",
17511
+ chorusLead: "Choir Aahs",
17512
+ chordAlt: "String Ensemble 2",
17513
+ sparkle: "Tinkle Bell",
17514
+ bassAlt: "Contrabass",
17515
+ harmonyAlt: "Choir Aahs"
17516
+ },
17517
+ ambient_cloud: {
17518
+ displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
17519
+ description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
17520
+ melody: "Lead 6 (voice)",
17521
+ submelody: "Music Box",
17522
+ bass: "Synth Bass 1",
17523
+ chord: "Pad 7 (halo)",
17524
+ solo: "Lead 3 (calliope)",
17525
+ chorusLead: "Synth Choir",
17526
+ chordAlt: "Pad 5 (bowed)",
17527
+ sparkle: "FX 3 (crystal)",
17528
+ bassAlt: "Synth Bass 2",
17529
+ harmonyAlt: "Synth Choir"
17530
+ },
17531
+ retro_game: {
17532
+ displayName: "8-bit \u30EC\u30C8\u30ED",
17533
+ description: "\u77E9\u5F62\u6CE2\u3092\u60F3\u8D77\u3055\u305B\u308B\u3001\u521D\u671F\u30B2\u30FC\u30E0\u6A5F\u306E\u3088\u3046\u306A\u61D0\u304B\u3057\u3044\u97FF\u304D\u3002",
17534
+ melody: "Lead 1 (square)",
17535
+ submelody: "Lead 2 (sawtooth)",
17536
+ bass: "Synth Bass 1",
17537
+ chord: "Clavinet",
17538
+ solo: "Lead 8 (bass + lead)",
17539
+ chorusLead: "Lead 4 (chiff)",
17540
+ chordAlt: "Lead 5 (charang)",
17541
+ sparkle: "Xylophone",
17542
+ bassAlt: "Synth Bass 2",
17543
+ harmonyAlt: "Lead 6 (voice)"
17544
+ }
17545
+ };
17546
+ var GM_INSTRUMENT_RANGE = {
17547
+ // 鍵盤・音板
17548
+ "Acoustic Grand Piano": [21, 108],
17549
+ "Electric Piano 1": [28, 103],
17550
+ Clavinet: [36, 96],
17551
+ Celesta: [60, 108],
17552
+ Glockenspiel: [79, 108],
17553
+ "Music Box": [72, 108],
17554
+ Vibraphone: [53, 89],
17555
+ Kalimba: [60, 84],
17556
+ "Orchestral Harp": [23, 104],
17557
+ // 弦・撥弦
17558
+ "Acoustic Guitar (steel)": [40, 83],
17559
+ "Acoustic Guitar (nylon)": [40, 83],
17560
+ "Electric Guitar (clean)": [40, 86],
17561
+ "Electric Guitar (jazz)": [40, 86],
17562
+ "Overdriven Guitar": [40, 88],
17563
+ "Distortion Guitar": [40, 88],
17564
+ Violin: [55, 103],
17565
+ Cello: [36, 76],
17566
+ "String Ensemble 1": [28, 100],
17567
+ "Tremolo Strings": [28, 100],
17568
+ "Pizzicato Strings": [28, 96],
17569
+ Sitar: [48, 79],
17570
+ Shamisen: [48, 84],
17571
+ Koto: [41, 77],
17572
+ // ベース
17573
+ "Acoustic Bass": [28, 60],
17574
+ "Electric Bass (finger)": [28, 67],
17575
+ "Electric Bass (pick)": [28, 67],
17576
+ "Fretless Bass": [28, 67],
17577
+ "Synth Bass 1": [24, 72],
17578
+ "Synth Bass 2": [24, 72],
17579
+ // 管
17580
+ Flute: [60, 96],
17581
+ "Pan Flute": [60, 91],
17582
+ Shakuhachi: [62, 86],
17583
+ Ocarina: [60, 84],
17584
+ Harmonica: [60, 96],
17585
+ Bagpipe: [62, 86],
17586
+ Shanai: [60, 86],
17587
+ "Tenor Sax": [44, 75],
17588
+ Trumpet: [55, 82],
17589
+ "Muted Trumpet": [55, 82],
17590
+ "French Horn": [41, 77],
17591
+ "Brass Section": [41, 84],
17592
+ // 声・打・オルガン
17593
+ "Choir Aahs": [43, 84],
17594
+ "Synth Choir": [43, 84],
17595
+ "Steel Drums": [55, 86],
17596
+ "Taiko Drum": [30, 60],
17597
+ Timpani: [36, 57],
17598
+ "Rock Organ": [36, 96],
17599
+ // シンセ(実物が無いので一般的な使用域)
17600
+ "Synth Brass 1": [36, 96],
17601
+ "Lead 1 (square)": [36, 96],
17602
+ "Lead 2 (sawtooth)": [36, 96],
17603
+ "Lead 3 (calliope)": [48, 96],
17604
+ "Lead 4 (chiff)": [48, 96],
17605
+ "Lead 5 (charang)": [40, 96],
17606
+ "Lead 6 (voice)": [43, 91],
17607
+ "Lead 7 (fifths)": [36, 84],
17608
+ "Lead 8 (bass + lead)": [28, 91],
17609
+ "Pad 2 (warm)": [24, 96],
17610
+ "Pad 3 (polysynth)": [24, 96],
17611
+ "Pad 7 (halo)": [24, 96],
17612
+ "Pad 8 (sweep)": [24, 96]
17613
+ };
17614
+ var GM_BRIGHT_CEILING = {
17615
+ Glockenspiel: 72,
17616
+ "Tinkle Bell": 72,
17617
+ "Music Box": 79,
17618
+ Celesta: 84,
17619
+ Kalimba: 79,
17620
+ "Steel Drums": 84,
17621
+ "FX 3 (crystal)": 79
17622
+ };
17623
+ var OCTAVE_FIT_TOLERANCE = 3;
17624
+ var fitInstrumentOctave = (semitoneRange, instrument, wanted) => {
17625
+ const range = GM_INSTRUMENT_RANGE[instrument];
17626
+ if (!range || !semitoneRange) return wanted;
17627
+ const hi = Math.min(range[1], GM_BRIGHT_CEILING[instrument] ?? range[1]);
17628
+ let octave = wanted;
17629
+ while (octave > wanted - 2) {
17630
+ if (semitoneRange[1] + octave * 12 <= hi + OCTAVE_FIT_TOLERANCE) break;
17631
+ octave--;
17632
+ }
17633
+ return octave;
17634
+ };
17635
+
17636
+ // src/advanced-layers.ts
17637
+ var buildAdvancedLayers = (song, config) => {
17638
+ const { edo, stepsPerBar, preset } = config;
17639
+ const semitoneRange = (notes) => {
17640
+ if (notes.length === 0) return null;
17641
+ let lo = Number.POSITIVE_INFINITY;
17642
+ let hi = Number.NEGATIVE_INFINITY;
17643
+ for (const n of notes) {
17644
+ const semi = n.pitchUnits / UNITS_PER_SEMITONE;
17645
+ if (semi < lo) lo = semi;
17646
+ if (semi > hi) hi = semi;
17647
+ }
17648
+ return [Math.round(lo), Math.round(hi)];
17649
+ };
17650
+ const fit = (notes, slot, wanted) => fitInstrumentOctave(semitoneRange(notes), preset[slot], wanted);
17651
+ const kindAtBar = (bar) => song.sections.find(
17652
+ (sec) => bar >= sec.startBar && bar < sec.startBar + sec.bars
17653
+ )?.kind ?? null;
17654
+ const onlyIn = (notes, kinds) => {
17655
+ if (!kinds) return notes;
17656
+ const want = new Set(kinds);
17657
+ return notes.filter((n) => {
17658
+ const kind = kindAtBar(Math.floor(n.startStep / stepsPerBar));
17659
+ return kind !== null && want.has(kind);
17660
+ });
17661
+ };
17662
+ const chordNotes = (pattern, velocityShift = 0) => buildChordPlacements({
17663
+ edo,
17664
+ chordStr: song.chordProgression,
17665
+ patternType: pattern,
17666
+ rootShift: song.rootShift,
17667
+ bpm: song.bpm,
17668
+ stepsPerBar
17669
+ }).map((p) => ({
17670
+ startStep: p.startStep,
17671
+ pitchUnits: p.pitchUnits,
17672
+ durationSteps: p.durationSteps,
17673
+ velocity: Math.max(30, p.velocity + velocityShift)
17674
+ }));
17675
+ const plan = song.arrange;
17676
+ const kindsPresent = [...new Set(song.sections.map((sec) => sec.kind))];
17677
+ const LOUD = ["chorus", "drop_chorus"];
17678
+ const loudKinds = kindsPresent.filter((k) => LOUD.includes(k));
17679
+ const quietKinds = kindsPresent.filter((k) => !LOUD.includes(k));
17680
+ const splittable = loudKinds.length > 0 && quietKinds.length > 0;
17681
+ const leadNotes = plan.lead ? onlyIn(song.melody, plan.lead.sections) : [];
17682
+ const leadOctave = fit(leadNotes, "chorusLead", plan.lead?.octave ?? 0);
17683
+ const leadLayer = {
17684
+ index: 1,
17685
+ notes: leadNotes,
17686
+ octave: leadOctave,
17687
+ // ユニゾンで重ねるときは、オクターブ上より前に出やすいので少し引く。
17688
+ volume: leadOctave === 0 ? 54 : 62,
17689
+ slot: "chorusLead"
17690
+ };
17691
+ const bassNotes = plan.bassLayer ? onlyIn(song.bass, plan.bassLayer.sections) : [];
17692
+ const bassOctave = fit(bassNotes, "bass", plan.bassLayer?.octave ?? 0);
17693
+ const wantBassLayer = plan.bassLayer !== null && bassOctave !== 0;
17694
+ const bassLayer = wantBassLayer ? {
17695
+ index: 5,
17696
+ notes: bassNotes,
17697
+ octave: bassOctave,
17698
+ volume: 58,
17699
+ slot: "bass"
17700
+ } : {
17701
+ index: 5,
17702
+ notes: splittable ? onlyIn(song.bass, loudKinds) : [],
17703
+ octave: 0,
17704
+ volume: 92,
17705
+ slot: "bassAlt"
17706
+ };
17707
+ const padNotes = onlyIn(song.pad, plan.padSections);
17708
+ const layers = [
17709
+ { index: 0, notes: song.melody, octave: 0, volume: 104, slot: "melody" },
17710
+ leadLayer,
17711
+ // t2 ハモリ。**セクションで分割しない。**
17712
+ // 一度 t12 と loud/quiet で分けてみたが、ハモリは音の87%が盛り上がる側にあり、
17713
+ // 71%の曲は静かな側に1音も無い——分割すると t2 が空になって中身が t12 へ移る。
17714
+ // 「トラック番号と役割の対応は固定」(ピアノロールのどこに何があるかが曲ごとに
17715
+ // 動くとユーザーが編集できない)という原則にも反するので、ここは丸ごと持つ。
17716
+ { index: 2, notes: song.harmony, octave: 0, volume: 82 },
17717
+ // t3 サブメロ。t14 が間奏ソロを持たないとき、そちらへ盛り上がる側を渡して
17718
+ // こちらは静かな側だけ持つ。対旋律・合いの手がセクションで音色替えする。
17719
+ {
17720
+ index: 3,
17721
+ notes: song.solo.length === 0 && splittable ? onlyIn(song.submelody, quietKinds) : song.submelody,
17722
+ octave: 0,
17723
+ volume: 86,
17724
+ slot: "submelody"
17725
+ },
17726
+ // t4。t5 が `bassAlt` で盛り上がる側を持つときは、こちらは静かな側だけ持つ
17727
+ // (分割であって間引きではない——2本合わせて元のベースが漏れなく鳴る)。
17728
+ {
17729
+ index: 4,
17730
+ notes: !wantBassLayer && splittable ? onlyIn(song.bass, quietKinds) : song.bass,
17731
+ octave: 0,
17732
+ volume: 92,
17733
+ slot: "bass"
17734
+ },
17735
+ bassLayer,
17736
+ // **パッドは伴奏用の楽器で、伴奏より1.5オクターブ高いところを鳴らす。**
17737
+ // そのまま置くとナイロンギターで10半音、カリンバで9半音、尺八で7半音ぶん
17738
+ // 音域を突き抜ける。楽器に合わせて下げる。
17739
+ {
17740
+ index: 6,
17741
+ notes: padNotes,
17742
+ octave: fit(padNotes, "chord", 0),
17743
+ volume: 64,
17744
+ slot: "chord"
17745
+ }
17746
+ ];
17747
+ const backingVolumes = [62, 54, 50];
17748
+ for (let i2 = 0; i2 < 3; i2++) {
17749
+ const layer = plan.backing[i2];
17750
+ layers.push({
17751
+ index: 7 + i2,
17752
+ notes: layer ? onlyIn(chordNotes(layer.pattern), layer.sections) : [],
17753
+ octave: layer?.octave ?? 0,
17754
+ volume: backingVolumes[i2],
17755
+ slot: i2 % 2 === 0 ? "chordAlt" : "chord"
17756
+ });
17757
+ }
17758
+ const sparkleNotes = plan.sparkle ? onlyIn(chordNotes(plan.sparkle.pattern, -14), plan.sparkle.sections) : [];
17759
+ layers.push(
17760
+ {
17761
+ index: 10,
17762
+ notes: sparkleNotes,
17763
+ // 装飾は伴奏と別の音色にする(以前はここも `chord` の使い回しだった)。
17764
+ octave: fit(sparkleNotes, "sparkle", plan.sparkle?.octave ?? 0),
17765
+ volume: 56,
17766
+ slot: "sparkle"
17767
+ },
17768
+ // 掛け合い(デュエット)の相手。**歌入り作曲のときだけ**中身が入る。
17769
+ { index: 11, notes: [], octave: 0, volume: 104, slot: "melody" },
17770
+ // 2声目のハモリ(主旋律を上下から挟む3声)と、主旋律のオクターブ下の重ね。
17771
+ // どちらも曲ごとに出るかどうかが決まる(`song.vocal`)。
17772
+ // t12。3声目のハモリは曲ごとに出るかが決まり、実測28%。
17773
+ //
17774
+ // 出ない曲では**パッド(t6)が鳴っていないセクション**を、別の音色
17775
+ // (`harmonyAlt` =声もの・柔らかい持続音)で受け持つ。パッドはセクション種別の
17776
+ // 39%しか覆っておらず補集合は常に存在するので、ここは**いま何も鳴っていない
17777
+ // 場所に持続音を足す**ことになる——重ねでも写しでもなく、純粋な追加。
17778
+ song.harmony2.length > 0 ? { index: 12, notes: song.harmony2, octave: 0, volume: 74 } : (() => {
17779
+ const rest = plan.padSections ? kindsPresent.filter((k) => !plan.padSections?.includes(k)) : [];
17780
+ const notes = rest.length > 0 ? onlyIn(song.pad, rest) : [];
17781
+ return {
17782
+ index: 12,
17783
+ notes,
17784
+ octave: fit(notes, "harmonyAlt", 0),
17785
+ volume: 60,
17786
+ slot: "harmonyAlt"
17787
+ };
17788
+ })(),
17789
+ // t13。既定は**主旋律のオクターブ下の重ね**で、歌入り作曲ではここに歌詞が付く
17790
+ // (オクターブ下でハモる歌手。単なる写しではない)。
17791
+ //
17792
+ // ただしこの層は曲ごとに出るかどうかが決まり、実測で**18%しか鳴らない**。
17793
+ // 残り82%はトラックが1本まるごと遊ぶ。オクターブ等価の写しに常設の価値は
17794
+ // 無いが、**空けておく価値はもっと無い**ので、層が無い曲では
17795
+ // 「t1(サビ重ね)が担当しないセクションの主旋律を、別の楽器でなぞる」層に回す。
17796
+ // t1 は CHORUS/LOUD 側を持つので、こちらは静かな側を持つ——結果として
17797
+ // **セクションの境目で主旋律に付く音色が入れ替わる**。1トラック1楽器の制約下で
17798
+ // 「パートごとに楽器が変わる」を作れるのは、この書き分けだけ。
17799
+ song.octave.length > 0 ? {
17800
+ index: 13,
17801
+ notes: song.octave,
17802
+ octave: -1,
17803
+ volume: 56,
17804
+ slot: "melody"
17805
+ } : (() => {
17806
+ const leadKinds = new Set(plan.lead?.sections ?? []);
17807
+ const quiet = song.sections.filter((sec) => sec.spec.melody && !leadKinds.has(sec.kind)).map((sec) => sec.kind);
17808
+ const notes = quiet.length > 0 ? onlyIn(song.melody, quiet) : [];
17809
+ const slot = ["solo", "chorusLead", "submelody", "chordAlt"].find(
17810
+ (k) => preset[k] !== preset.melody && preset[k] !== preset.chord
17811
+ ) ?? "submelody";
17812
+ return {
17813
+ index: 13,
17814
+ notes,
17815
+ // ユニゾンで置く。**音色だけを変えるのが狙い**なので、
17816
+ // オクターブを動かすと「オクターブ写し」に戻ってしまう。
17817
+ octave: fit(notes, slot, 0),
17818
+ volume: 58,
17819
+ slot
17820
+ };
17821
+ })(),
17822
+ // **間奏のソロ。** 音が入るのは間奏の小節だけなので、この1本だけを
17823
+ // 別の楽器にしても他のセクションの鳴りは変わらない。歌の音域をそのまま
17824
+ // 渡すと管楽器が上へ抜ける(テナーサックスで10半音)ので、ここも合わせる。
17825
+ // t14。**間奏のソロ。** ただし間奏は既定の構成(`DEFAULT_SECTIONS`)に無く、
17826
+ // テンプレートも `jpop_standard` しか含まないので、実測で**0%**——設計上の
17827
+ // 出番が既定では一度も来ないトラックだった。
17828
+ // 間奏が無い曲では、サブメロの盛り上がる側を `solo` の音色で受け持つ
17829
+ // (t3 は静かな側。分割なので音数は増えない)。
17830
+ song.solo.length > 0 ? {
17831
+ index: 14,
17832
+ notes: song.solo,
17833
+ octave: fit(song.solo, "solo", 0),
17834
+ volume: 100,
17835
+ slot: "solo"
17836
+ } : (() => {
17837
+ const notes = splittable ? onlyIn(song.submelody, loudKinds) : [];
17838
+ return {
17839
+ index: 14,
17840
+ notes,
17841
+ octave: fit(notes, "solo", 0),
17842
+ volume: 86,
17843
+ slot: "solo"
17844
+ };
17845
+ })()
17846
+ );
17847
+ return layers;
17848
+ };
17849
+
16432
17850
  // src/delay.ts
16433
17851
  var DELAY_DIVISIONS = [
16434
17852
  { value: "4", label: "4\u5206", beats: 1 },
@@ -16849,6 +18267,34 @@ var buildUI = (target, options) => {
16849
18267
  <span class="dtm-grow"></span>
16850
18268
  <span class="dtm-hint" data-dtm="compose-key-hint"></span>
16851
18269
  </div>
18270
+ <div class="dtm-row" data-dtm="compose-scale-row">
18271
+ <span class="dtm-label">\u97F3\u968E</span>
18272
+ <select class="dtm-select" data-dtm="compose-scale" title="\u65CB\u5F8B\u304C\u4F7F\u3046\u97F3\u968E\u3092\u9078\u3073\u307E\u3059\u3002\u30D9\u30FC\u30B9\u8ABF\uFF08\u4E3B\u97F3\u306E\u9AD8\u3055\uFF09\u3068\u306F\u72EC\u7ACB\u3057\u305F\u8A2D\u5B9A\u3067\u3059">
18273
+ <option value="auto" title="\u30D9\u30FC\u30B9\u8ABF\u306E\u9577\u77ED\u306B\u5408\u308F\u305B\u3066\u3001\u967D\u97F3\u968E\uFF08\u9577\u8ABF\uFF09\u304B\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\uFF09\u3092\u4F7F\u3044\u307E\u3059">\u304A\u307E\u304B\u305B\uFF08\u5F93\u6765\u3069\u304A\u308A\uFF09</option>
18274
+ <option value="any" title="9\u3064\u306E\u97F3\u968E\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057\uFF08\u5168\u97F3\u968E\u304B\u3089\u62BD\u9078\uFF09</option>
18275
+ <optgroup label="\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF085\u97F3\u97F3\u968E\uFF09">
18276
+ <option value="yo" title="J-POP\u306E\u6A19\u6E96\u3002\u660E\u308B\u304F\u7D20\u76F4\u3067\u6B4C\u3044\u3084\u3059\u3044\u3002\u5F93\u6765\u306E\u9577\u8ABF\u3068\u540C\u3058">\u967D\u97F3\u968E\uFF08\u9577\u8ABF\u30DA\u30F3\u30BF\uFF09</option>
18277
+ <option value="minyo" title="\u308F\u3089\u3079\u6B4C\u30FB\u6C11\u8B21\u306E\u97F3\u968E\u3002\u7FF3\u308A\u304C\u3042\u308B\u304C\u6697\u3059\u304E\u306A\u3044\u3002\u5F93\u6765\u306E\u77ED\u8ABF\u3068\u540C\u3058">\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\u30DA\u30F3\u30BF\uFF09</option>
18278
+ <option value="ryukyu" title="\u6C96\u7E04\u97F3\u968E\u3002\u30EC\u3068\u30E9\u3092\u629C\u304D\u3001\u30D5\u30A1\u3068\u30B7\u3092\u67F1\u306B\u3059\u308B\u3002\u660E\u308B\u304F\u8DF3\u306D\u308B">\u7409\u7403\u97F3\u968E\uFF08\u6C96\u7E04\uFF09</option>
18279
+ <option value="miyakobushi" title="\u300E\u3055\u304F\u3089\u3055\u304F\u3089\u300F\u306E\u97F3\u968E\u3002\u4E3B\u97F3\u306E\u3059\u3050\u4E0A\u304C\u534A\u97F3\u3067\u3001\u7FF3\u308A\u304C\u6FC3\u3044">\u90FD\u7BC0\u97F3\u968E\uFF08\u9670\u97F3\u968E\uFF09</option>
18280
+ <option value="ritsu" title="\u96C5\u697D\u30FB\u58F0\u660E\u306E\u97F3\u968E\u3002\u534A\u97F3\u3092\u542B\u307E\u305A\u3001\u5E73\u3089\u3067\u8358\u91CD\u306B\u6D41\u308C\u308B">\u5F8B\u97F3\u968E\uFF08\u96C5\u697D\uFF09</option>
18281
+ </optgroup>
18282
+ <optgroup label="\u30C1\u30E3\u30FC\u30C1\u30E2\u30FC\u30C9\uFF087\u97F3\u97F3\u968E\uFF09">
18283
+ <option value="dorian" title="\u77ED\u8ABF\u3060\u304C6\u5EA6\u304C\u660E\u308B\u3044\u3002\u30B1\u30EB\u30C8\u30FB\u30ED\u30C3\u30AF\u30FB\u30B7\u30C6\u30A3\u30DD\u30C3\u30D7">\u30C9\u30EA\u30A2\u30F3</option>
18284
+ <option value="phrygian" title="\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3002\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\uFF0F\u30E1\u30BF\u30EB\u306E\u7DCA\u8FEB\u3057\u305F\u97FF\u304D">\u30D5\u30EA\u30B8\u30A2\u30F3</option>
18285
+ <option value="lydian" title="4\u5EA6\u304C\u9AD8\u304F\u3001\u6D6E\u904A\u3057\u3066\u5E83\u304C\u308B\u3002\u6620\u753B\u97F3\u697D\u30FB\u30B2\u30FC\u30E0\u306E\u7A7A\u306E\u8272">\u30EA\u30C7\u30A3\u30A2\u30F3</option>
18286
+ <option value="mixolydian" title="\u9577\u8ABF\u3060\u304C7\u5EA6\u304C\u4F4E\u3044\u3002\u30D6\u30EB\u30FC\u30B9\u30ED\u30C3\u30AF\u30FB\u6C11\u65CF\u97F3\u697D\u306E\u571F\u304F\u3055\u3055">\u30DF\u30AF\u30BD\u30EA\u30C7\u30A3\u30A2\u30F3</option>
18287
+ </optgroup>
18288
+ <optgroup label="\u7279\u6B8A\u97F3\u968E\uFF08\u97F3\u7A0B\u96C6\u5408\u3054\u3068\u5165\u308C\u66FF\u308F\u308B\uFF09">
18289
+ <option value="harmonic_minor" title="\u5C0E\u97F3\u30BD\u266F\u3092\u6301\u3064\u77ED\u8ABF\u3002\u58972\u5EA6\u304C\u6CE3\u304D\u3092\u4F5C\u308B\u3002\u30AF\u30E9\u30B7\u30C3\u30AF\u30FBV\u7CFB\u30FB\u5287\u4F34">\u548C\u58F0\u7684\u77ED\u97F3\u968E</option>
18290
+ <option value="hijaz" title="\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3001\u4E3B\u548C\u97F3\u306F\u9577\u4E09\u548C\u97F3\u3002\u4E2D\u6771\u30FB\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\u30FB\u30E1\u30BF\u30EB">\u30D2\u30B8\u30E3\u30FC\u30BA\uFF08\u30D5\u30EA\u30B8\u30A2\u30F3\u30FB\u30C9\u30DF\u30CA\u30F3\u30C8\uFF09</option>
18291
+ <option value="hungarian" title="\u58972\u5EA6\u304C2\u304B\u6240\u3002\u97F3\u968E\u306E\u4E2D\u3067\u3044\u3061\u3070\u3093\u8DF3\u306D\u305F\u3001\u7570\u56FD\u3081\u3044\u305F\u97FF\u304D">\u30CF\u30F3\u30AC\u30EA\u30A2\u30F3\u30FB\u30DE\u30A4\u30CA\u30FC\uFF08\u30B8\u30D7\u30B7\u30FC\uFF09</option>
18292
+ <option value="blues" title="\u30D6\u30EB\u30FC\u30CE\u30FC\u30C8\u5165\u308A\u306E6\u97F3\u97F3\u968E\u3002\u77ED3\u5EA6\u3067\u6B4C\u3044\u3001\u4F34\u594F\u306F\u95773\u5EA6\u3067\u9CF4\u308B">\u30D6\u30EB\u30FC\u30B9\u97F3\u968E</option>
18293
+ </optgroup>
18294
+ </select>
18295
+ <span class="dtm-grow"></span>
18296
+ <span class="dtm-hint" data-dtm="compose-scale-hint"></span>
18297
+ </div>
16852
18298
  </div>
16853
18299
  <div class="dtm-row">
16854
18300
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
@@ -17051,6 +18497,8 @@ var buildUI = (target, options) => {
17051
18497
  composeSectionsLen: sel("compose-sections-len"),
17052
18498
  composeKey: sel("compose-key"),
17053
18499
  composeKeyHint: sel("compose-key-hint"),
18500
+ composeScale: sel("compose-scale"),
18501
+ composeScaleHint: sel("compose-scale-hint"),
17054
18502
  macroComposeVocal: sel("macro-compose-vocal"),
17055
18503
  macroComposeInfo: sel("macro-compose-info"),
17056
18504
  macroClear: sel("macro-clear"),
@@ -17085,115 +18533,12 @@ var buildUI = (target, options) => {
17085
18533
  };
17086
18534
  };
17087
18535
 
17088
- // src/instrument-presets.ts
17089
- var INSTRUMENT_PRESETS = {
17090
- // --- STANDARD: 汎用性と完成度重視 ---
17091
- piano: {
17092
- displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
17093
- description: "\u6700\u3082\u7834\u7DBB\u3057\u306B\u304F\u3044\u69CB\u6210\u3002\u697D\u66F2\u5236\u4F5C\u306E\u30B9\u30B1\u30C3\u30C1\u306B\u3082\u6700\u9069\u3002",
17094
- melody: "Acoustic Grand Piano",
17095
- submelody: "Vibraphone",
17096
- bass: "Electric Bass (finger)",
17097
- chord: "Pad 2 (warm)"
17098
- },
17099
- acoustic: {
17100
- displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
17101
- description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
17102
- melody: "Acoustic Guitar (steel)",
17103
- submelody: "Harmonica",
17104
- bass: "Acoustic Bass",
17105
- chord: "Acoustic Guitar (nylon)"
17106
- },
17107
- jazz_night: {
17108
- displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
17109
- description: "Rhodes\u98A8\u306EEP\u3068\u30A6\u30C3\u30C9\u30D9\u30FC\u30B9\u306B\u3088\u308B\u3001\u5927\u4EBA\u3073\u305F\u30A2\u30F3\u30B5\u30F3\u30D6\u30EB\u3002",
17110
- melody: "Electric Piano 1",
17111
- submelody: "Flute",
17112
- bass: "Acoustic Bass",
17113
- chord: "Electric Guitar (jazz)"
17114
- },
17115
- // --- MODERN & VIBE: エッジの効いた現代的な響き ---
17116
- synth_pop: {
17117
- displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
17118
- description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
17119
- melody: "Lead 2 (sawtooth)",
17120
- submelody: "Lead 4 (chiff)",
17121
- bass: "Synth Bass 2",
17122
- chord: "Pad 3 (polysynth)"
17123
- },
17124
- cyber_punk: {
17125
- displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
17126
- description: "\u30C7\u30B8\u30BF\u30EB\u306A\u51B7\u305F\u3055\u3068\u6B6A\u307F\u304C\u6DF7\u3056\u308A\u5408\u3046\u3001\u672A\u6765\u7684\u306A\u97FF\u304D\u3002",
17127
- melody: "Lead 8 (bass + lead)",
17128
- submelody: "Lead 5 (charang)",
17129
- bass: "Synth Bass 2",
17130
- chord: "Pad 8 (sweep)"
17131
- },
17132
- rock: {
17133
- displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
17134
- description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
17135
- melody: "Distortion Guitar",
17136
- submelody: "Rock Organ",
17137
- bass: "Electric Bass (pick)",
17138
- chord: "Overdriven Guitar"
17139
- },
17140
- // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
17141
- orchestra: {
17142
- displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
17143
- description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
17144
- melody: "French Horn",
17145
- submelody: "Pizzicato Strings",
17146
- bass: "Cello",
17147
- chord: "Tremolo Strings"
17148
- },
17149
- japanese_wa: {
17150
- displayName: "\u548C\u98A8\u30FB\u96C5",
17151
- description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
17152
- melody: "Koto",
17153
- submelody: "Shamisen",
17154
- bass: "Taiko Drum",
17155
- chord: "Shakuhachi"
17156
- },
17157
- arabic_exotic: {
17158
- displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
17159
- description: "\u30B7\u30BF\u30FC\u30EB\u3084\u30D0\u30B0\u30D1\u30A4\u30D7\u306B\u3088\u308B\u3001\u7570\u56FD\u60C5\u7DD2\u6EA2\u308C\u308B\u30B5\u30A6\u30F3\u30C9\u3002",
17160
- melody: "Sitar",
17161
- submelody: "Bagpipe",
17162
- bass: "Fretless Bass",
17163
- chord: "Kalimba"
17164
- },
17165
- // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
17166
- fantasy_rpg: {
17167
- displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
17168
- description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
17169
- melody: "Ocarina",
17170
- submelody: "Celesta",
17171
- bass: "Timpani",
17172
- chord: "Orchestral Harp"
17173
- },
17174
- ambient_cloud: {
17175
- displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
17176
- description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
17177
- melody: "Lead 6 (voice)",
17178
- submelody: "Music Box",
17179
- bass: "Synth Bass 1",
17180
- chord: "Pad 7 (halo)"
17181
- },
17182
- retro_game: {
17183
- displayName: "8-bit \u30EC\u30C8\u30ED",
17184
- description: "\u77E9\u5F62\u6CE2\u3092\u60F3\u8D77\u3055\u305B\u308B\u3001\u521D\u671F\u30B2\u30FC\u30E0\u6A5F\u306E\u3088\u3046\u306A\u61D0\u304B\u3057\u3044\u97FF\u304D\u3002",
17185
- melody: "Lead 1 (square)",
17186
- submelody: "Lead 2 (sawtooth)",
17187
- bass: "Synth Bass 1",
17188
- chord: "Clavinet"
17189
- }
17190
- };
17191
-
17192
18536
  // src/macro-state.ts
17193
18537
  var MACRO_STORAGE_KEYS = {
17194
18538
  template: "dtm-macro:template",
17195
18539
  sections: "dtm-macro:sections",
17196
18540
  key: "dtm-macro:key",
18541
+ scale: "dtm-macro:scale",
17197
18542
  shift: "dtm-macro:shift",
17198
18543
  transpose: "dtm-macro:transpose"
17199
18544
  };
@@ -19353,6 +20698,7 @@ var COMPOSE_INFO_HTML = `
19353
20698
  <li><strong>\u30E1\u30ED\u30C7\u30A3\u306E\u66F8\u6CD5</strong>\u2014\u2014\u8D70\u53E5\u306E\u5F62\uFF08\u97F3\u968E\uFF0F\u6298\u308A\u8FD4\u3057\uFF0F\u5206\u6563\u548C\u97F3\uFF0F\u30B8\u30B0\u30B6\u30B0\uFF09\u3001\u3064\u306A\u304E\u306E\u5F62\uFF08\u5C71\u306A\u308A\uFF0F\u8C37\uFF0F\u4E0A\u884C\uFF0F\u4E0B\u884C\uFF0F\u3046\u306D\u308A\uFF0F\u8EF8\u97F3\u307E\u308F\u308A\uFF09\u3001\u7D42\u6B62\u306E\u5F62\uFF08\u9806\u6B21\u4E0B\u964D\uFF0F\u30BD\u30DF\u30C9\uFF0F\u8DF3\u306D\u4E0A\u304C\u308A\uFF0F\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\uFF09\u3001\u30E2\u30C1\u30FC\u30D5\u306E\u539F\u578B\u3001\u8DF3\u8E8D\u306E\u6DF7\u305C\u5177\u5408\u3001\u958B\u59CB\u97F3\u3001\u57FA\u6E96\u306E\u523B\u307F\uFF088\u5206\uFF0F16\u5206\uFF0F\u4E09\u9023\uFF09\u3001\u30B9\u30A6\u30A3\u30F3\u30B0\u91CF</li>
19354
20699
  <li><strong>\u30D9\u30FC\u30B9\u306E\u594F\u6CD5</strong>\uFF084\u5206\u6253\u3061\uFF0F\u30AA\u30EB\u30BF\u30CD\u30A4\u30C8\uFF0F2\u5206\uFF0F8\u5206\u30C9\u30E9\u30A4\u30D6\uFF0F\u30B7\u30F3\u30B3\u30DA\uFF0F\u30A6\u30A9\u30FC\u30AD\u30F3\u30B0\uFF0F\u30AA\u30AF\u30BF\u30FC\u30D6\uFF09</li>
19355
20700
  <li><strong>\u30B5\u30D6\u30E1\u30ED\u306E\u66F8\u304D\u65B9</strong>\u2014\u2014\u5408\u3044\u306E\u624B\uFF08\u30E1\u30ED\u30C7\u30A3\u304C\u4F11\u3093\u3060\u9699\u9593\u306B\u3060\u3051\u5165\u308B\uFF09\u3001\u30CF\u30E2\u30EA\uFF08\u30E1\u30ED\u30C7\u30A3\u306E\u30EA\u30BA\u30E0\u3092\u306A\u305E\u3063\u30663\u5EA6\u30FB6\u5EA6\u4E0B\u3092\u6B4C\u3046\uFF09\u3001\u5BFE\u65CB\u5F8B\uFF088\u5206\u3067\u30E1\u30ED\u30C7\u30A3\u3068\u53CD\u884C\u3059\u308B\uFF09\u3001\u4FDD\u7D9A\u97F3\uFF08\u540C\u3058\u97F3\u3092\u4F38\u3070\u3057\u7D9A\u3051\u308B\uFF09\u3001\u30D1\u30C3\u30C9\u3002\u5C0F\u7BC0\u3054\u3068\u306B\u3001\u30E1\u30ED\u30C7\u30A3\u304C\u606F\u7D99\u304E\u3057\u3066\u3044\u308B\u5834\u6240\u3067\u306F\u81EA\u52D5\u3067\u5408\u3044\u306E\u624B\u306B\u5207\u308A\u66FF\u308F\u308A\u307E\u3059\u3002</li>
20701
+ <li><strong>\u7DE8\u66F2\u30D7\u30E9\u30F3</strong>\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306E\u307F\uFF09\u2014\u2014\u4F34\u594F\u3092\u4F55\u5C64\u306B\u3059\u308B\u304B\u3001\u305D\u308C\u305E\u308C\u3069\u306E\u594F\u6CD5\u3067\u3069\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u9CF4\u3089\u3059\u304B\u3001\u30B5\u30D3\u306E\u91CD\u306D\u3092\u51FA\u3059\u304B\u30FB\u30E6\u30CB\u30BE\u30F3\u304B\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u304B\u3001\u88C5\u98FE\u3092\u3069\u3053\u306B\u7F6E\u304F\u304B\u3002400\u66F2\u5F15\u304F\u3068392\u901A\u308A\u306E\u578B\u304C\u51FA\u307E\u3059\u3002</li>
19356
20702
  </ul>
19357
20703
  <p>\u3055\u3089\u306B\u3001\u76F4\u524D\u306B\u4F5C\u3063\u305F5\u66F2\u3068\u7279\u5FB4\u304C\u4F3C\u3066\u3044\u308B\u5019\u88DC\u306B\u306F\u6E1B\u70B9\u3057\u3066\u3044\u307E\u3059\u3002\u7D9A\u3051\u3066\u62BC\u3057\u305F\u3068\u304D\u306B\u4F3C\u305F\u66F2\u304C\u4E26\u3070\u306A\u3044\u3088\u3046\u306B\u3059\u308B\u305F\u3081\u3067\u3059\u3002</p>
19358
20704
  <h4>\u3067\u304D\u3042\u304C\u308A\u306E\u9078\u3073\u65B9</h4>
@@ -19371,6 +20717,18 @@ var COMPOSE_INFO_HTML = `
19371
20717
  <li><strong>\u639B\u3051\u5408\u3044\uFF08\u30C7\u30E5\u30A8\u30C3\u30C8\uFF09</strong> \u2014 4\u5272\u307B\u3069\u306E\u66F2\u304C2\u4EBA\u6B4C\u3044\u306B\u306A\u308A\u307E\u3059\u3002\u30BB\u30AF\u30B7\u30E7\u30F3\u3054\u3068\uFF0FA\u30E1\u30ED\u30672\u5C0F\u7BC0\u3054\u3068\uFF0F\u639B\u3051\u5408\u3044\u30B5\u30D3\uFF0FA\u30E1\u30ED\u3060\u3051\u3001\u306E4\u901A\u308A\u3002<strong>\u53D7\u3051\u6E21\u3057\u306F\u5C0F\u7BC0\u7DDA\u3074\u3063\u305F\u308A\u3067\u306F\u306A\u304F\u3001\u6B21\u306E\u4EBA\u304C\u624B\u524D\u304B\u3089\u98DF\u3044\u6C17\u5473\u306B\u5165\u308A\u307E\u3059\u3002</strong>\u4E00\u7DD2\u306B\u6B4C\u3046\u30B5\u30D3\u3067\u306F\u76F8\u65B9\u304C\u30CF\u30E2\u30EA\u3078\u56DE\u308A\u307E\u3059\uFF08\u540C\u3058\u97F3\u3092\u306A\u305E\u308B\u30E6\u30CB\u30BE\u30F3\u306B\u306F\u3057\u307E\u305B\u3093\uFF09\u3002</li>
19372
20718
  </ul>
19373
20719
  <p>\u30B7\u30F3\u30D7\u30EB\u30E2\u30FC\u30C9\uFF084\u30C8\u30E9\u30C3\u30AF\uFF09\u306F\u72EC\u5531\u3067\u3059\u30024\u672C\u304C\u57CB\u307E\u3063\u3066\u3044\u3066\u30CF\u30E2\u30EA\u306E\u7F6E\u304D\u5834\u6240\u304C\u7121\u3044\u305F\u3081\u3067\u3059\u3002</p>
20720
+ <h4>\u30BB\u30AF\u30B7\u30E7\u30F3\u3067\u697D\u5668\u30FB\u4F34\u594F\u304C\u5909\u308F\u308B\uFF08\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306E\u307F\uFF09</h4>
20721
+ <p>1\u672C\u306E\u30C8\u30E9\u30C3\u30AF\u306F\u3001\u66F2\u306E\u6700\u521D\u304B\u3089\u6700\u5F8C\u307E\u30671\u3064\u306E\u697D\u5668\u3067\u9CF4\u308A\u307E\u3059\u3002\u3064\u307E\u308A<strong>\u300C\u30B5\u30D3\u3060\u3051\u5225\u306E\u697D\u5668\u306B\u3059\u308B\u300D\u306F\u30011\u672C\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u4E2D\u3067\u306F\u4F5C\u308C\u307E\u305B\u3093</strong>\u3002\u4F5C\u308C\u308B\u306E\u306F\u3001\u9CF4\u3089\u3057\u305F\u3044\u533A\u9593\u3092\u5225\u306E\u30C8\u30E9\u30C3\u30AF\u3078\u66F8\u304D\u5206\u3051\u305F\u3068\u304D\u3060\u3051\u3067\u3059\u3002\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\uFF0815\u30C8\u30E9\u30C3\u30AF\uFF09\u304C15\u672C\u3042\u308B\u306E\u306F\u305D\u306E\u305F\u3081\u3067\u3001\u300C\u4F5C\u66F2\u300D\u306F\u5834\u6240\u3054\u3068\u306B\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u307E\u3059\u3002</p>
20722
+ <ul>
20723
+ <li><strong>\u4F34\u594F\u306E\u624B\u89E6\u308A\u304C\u30BB\u30AF\u30B7\u30E7\u30F3\u3067\u5909\u308F\u308B</strong> \u2014 \u540C\u3058\u30B3\u30FC\u30C9\u9032\u884C\u3092\u3001\u30D6\u30ED\u30C3\u30AF\u30FB\u30A2\u30EB\u30DA\u30B8\u30AA\u30FB\u88CF\u62CD\u30FB\u516B\u5206\u98DF\u3044\u30FB\u5206\u6563\u306E\u4E2D\u304B\u3089<strong>\u5225\u3005\u306E\u594F\u6CD5</strong>\u30671\u301C3\u5C64\u306B\u91CD\u306D\u307E\u3059\u3002\u3069\u306E\u594F\u6CD5\u3092\u3069\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u3067\u9CF4\u3089\u3059\u304B\u306F\u66F2\u3054\u3068\u306B\u5F15\u304F\u306E\u3067\u3001A\u30E1\u30ED\u3068\u30B5\u30D3\u3067\u4F34\u594F\u306E\u523B\u307F\u65B9\u305D\u306E\u3082\u306E\u304C\u5909\u308F\u308A\u307E\u3059\u3002\u66F2\u5168\u4F53\u3092\u901A\u308B\u300C\u5730\u300D\u306E\u5C64\u304C1\u3064\u3042\u308A\u3001\u6B8B\u308A\u306F\u8DB3\u3059\u5834\u6240\u3092\u7D5E\u308A\u307E\u3059\u3002</li>
20724
+ <li><strong>\u30B5\u30D3\u306E\u91CD\u306D</strong> \u2014 \u4E3B\u65CB\u5F8B\u3092\u3082\u30461\u672C\u306E\u5225\u697D\u5668\uFF08\u30D7\u30EA\u30BB\u30C3\u30C8\u306B\u3088\u3063\u3066\u30D6\u30E9\u30B9\u30FB\u30B9\u30C8\u30EA\u30F3\u30B0\u30B9\u30FB\u30B0\u30ED\u30C3\u30B1\u30F3\u306A\u3069\uFF09\u3067\u91CD\u306D\u307E\u3059\u3002<strong>\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3068\u30E6\u30CB\u30BE\u30F3\u306E\u4E21\u65B9\u3092\u5F15\u304D\u307E\u3059</strong>\u2014\u2014\u540C\u3058\u9AD8\u3055\u3092\u5225\u306E\u697D\u5668\u3067\u91CD\u306D\u308B\u30682\u3064\u306E\u97F3\u8272\u304C\u6EB6\u3051\u3066\u5225\u306E\u97F3\u8272\u306B\u306A\u308B\u306E\u3067\u3001\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u308B\u3088\u308A\u60C5\u5831\u91CF\u304C\u591A\u3044\u3053\u3068\u304C\u3042\u308A\u307E\u3059\u3002\u91CD\u306D\u306A\u3044\u66F2\u3082\u3042\u308A\u307E\u3059\u3002</li>
20725
+ <li><strong>\u9593\u594F\u306E\u30BD\u30ED</strong> \u2014 \u9593\u594F\u306F\u6B4C\u304C\u4F11\u3080\u5834\u6240\u3067\u3042\u3063\u3066\u3001\u97F3\u697D\u304C\u4F11\u3080\u5834\u6240\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u6B4C\u30E1\u30ED\u306E\u4EE3\u308F\u308A\u306B\u5668\u697D\u306E\u30BD\u30ED\u3092\u66F8\u304D\u3001\u5C02\u7528\u306E\u30C8\u30E9\u30C3\u30AF\u3067\u9CF4\u3089\u3057\u307E\u3059\u3002\u30D7\u30EA\u30BB\u30C3\u30C8\u306B\u3088\u3063\u3066\u6B6A\u307F\u30AE\u30BF\u30FC\u30FB\u30B5\u30C3\u30AF\u30B9\u30FB\u5C3A\u516B\u306A\u3069\u306B\u5909\u308F\u308A\u307E\u3059\u3002\u7D20\u6750\u306F\u30B5\u30D3\u3068\u540C\u3058\u306A\u306E\u3067\u3001\u9593\u594F\u304C\u30B5\u30D3\u306E\u4E3B\u984C\u3092\u5F3E\u304F\u5F62\u306B\u306A\u308A\u307E\u3059\u3002</li>
20726
+ <li><strong>\u30A6\u30EF\u30E2\u30CE</strong> \u2014 \u304D\u3089\u3073\u3084\u304B\u306A\u88C5\u98FE\u3092\u3001\u76DB\u308A\u4E0A\u304C\u308B\u30BB\u30AF\u30B7\u30E7\u30F3\u306B\u3060\u3051\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3067\u8DB3\u3057\u307E\u3059\u3002<strong>\u5730\u306E\u4F34\u594F\u3068\u540C\u3058\u594F\u6CD5\u306F\u4F7F\u3044\u307E\u305B\u3093</strong>\u2014\u2014\u540C\u3058\u3082\u306E\u3092\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u305F\u3060\u3051\u306E\u5C64\u306F\u88C5\u98FE\u3067\u306F\u306A\u304F\u5199\u3057\u3060\u304B\u3089\u3067\u3059\u3002\u30D6\u30ED\u30C3\u30AF\u3082\u5916\u3057\u307E\u3059\uFF08\u548C\u97F3\u3092\u4E38\u3054\u3068\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3067\u9CF4\u3089\u3059\u306E\u306F\u88C5\u98FE\u3067\u306F\u306A\u304F\u58C1\u306B\u306A\u308A\u307E\u3059\uFF09\u3002</li>
20727
+ </ul>
20728
+ <p><strong>\u697D\u5668\u306E\u97F3\u57DF\u306B\u5408\u308F\u305B\u3066\u30AA\u30AF\u30BF\u30FC\u30D6\u3092\u4E0B\u3052\u307E\u3059\u3002</strong>1\u30C8\u30E9\u30C3\u30AF1\u697D\u5668\u3067\u3001\u3057\u304B\u3082\u5C64\u3054\u3068\u306B\u30AA\u30AF\u30BF\u30FC\u30D6\u3092\u5909\u3048\u308B\u306E\u3067\u3001\u6C7A\u3081\u305F\u30AA\u30AF\u30BF\u30FC\u30D6\u304C\u305D\u306E\u697D\u5668\u306E\u51FA\u305B\u306A\u3044\u9AD8\u3055\u306B\u306A\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059\u3002\u5B9F\u6E2C\u3059\u308B\u3068\u3001\u30B5\u30D3\u306E\u91CD\u306D\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A\u3052\u308B\u6307\u5B9A\u306F\u30DF\u30E5\u30FC\u30C8\u30C8\u30E9\u30F3\u30DA\u30C3\u30C8\u3084\u30C8\u30E9\u30F3\u30DA\u30C3\u30C8\u306714\u534A\u97F3\u3076\u3093\u3001\u30B3\u30FC\u30C9\u30D1\u30C3\u30C9\uFF08\u5B9F\u97F377\u301C93\uFF09\u306F\u30CA\u30A4\u30ED\u30F3\u30AE\u30BF\u30FC\u306710\u534A\u97F3\u30FB\u30AB\u30EA\u30F3\u30D0\u30679\u534A\u97F3\u3076\u3093\u97F3\u57DF\u3092\u7A81\u304D\u629C\u3051\u3066\u3044\u307E\u3057\u305F\u3002\u30B5\u30F3\u30D7\u30EB\u304C\u5F15\u304D\u4F38\u3070\u3055\u308C\u3066<strong>\u91D1\u5207\u308A\u97F3</strong>\u306B\u306A\u308A\u3001\u8074\u304D\u624B\u306B\u306F\u300C\u8033\u304C\u75DB\u3044\u300D\u3068\u3057\u304B\u611F\u3058\u3089\u308C\u307E\u305B\u3093\u3002\u305D\u3053\u3067\u3001\u9CF4\u3089\u3059\u97F3\u57DF\u304C\u305D\u306E\u697D\u5668\u306E\u5B9F\u7528\u4E0A\u9650\u306B\u53CE\u307E\u308B\u3068\u3053\u308D\u307E\u3067<strong>\u30AA\u30AF\u30BF\u30FC\u30D6\u3092\u4E0B\u3052\u3066\u304B\u3089</strong>\u7F6E\u304D\u307E\u3059\uFF08\u30C8\u30E9\u30C3\u30AF\u306E\u30AA\u30AF\u30BF\u30FC\u30D6\u8A2D\u5B9A\u306F\u3001\u307E\u3055\u306B\u3053\u3046\u3044\u3046\u300C\u5F97\u610F\u306A\u97F3\u57DF\u304C\u504F\u3063\u305F\u97F3\u6E90\u300D\u3092\u4F7F\u3048\u308B\u3088\u3046\u306B\u3059\u308B\u305F\u3081\u306B\u5728\u308B\u3082\u306E\u3067\u3059\uFF09\u3002<strong>\u4E0B\u3052\u308B\u65B9\u5411\u306B\u3057\u304B\u52D5\u304B\u3057\u307E\u305B\u3093</strong>\u2014\u2014\u4E0A\u3052\u308B\u5074\u304C\u75DB\u307F\u3092\u4F5C\u308B\u5074\u306A\u306E\u3067\u3002</p>
20729
+ <p>\u5B9F\u7269\u306E\u97F3\u57DF\u306B\u53CE\u307E\u3063\u3066\u3044\u3066\u3082\u75DB\u304F\u306A\u308B\u97F3\u8272\u304C\u3042\u308A\u307E\u3059\u3002\u30B0\u30ED\u30C3\u30B1\u30F3\u306F\u5B9F\u7269\u306E\u97F3\u57DF\u304CG5\u301CC8\u306A\u306E\u3067\u3001\u65CB\u5F8B\u306E\u97F3\u57DF\uFF08\u301CC6\uFF09\u306F\u300C\u4F59\u88D5\u3067\u7BC4\u56F2\u5185\u300D\u3067\u3059\u304C\u3001\u91D1\u5C5E\u4F53\u306E\u500D\u97F3\u306F\u4EBA\u306E\u8033\u304C\u3044\u3061\u3070\u3093\u654F\u611F\u306A2\u301C4kHz\u306B\u96C6\u307E\u308B\u305F\u3081\u3001\u305D\u306E\u9AD8\u3055\u3067\u9CF4\u3089\u3057\u7D9A\u3051\u308B\u3068\u523A\u3055\u308A\u307E\u3059\u3002\u3053\u3046\u3044\u3046\u97F3\u8272\u306B\u306F\u97F3\u57DF\u3068\u306F\u5225\u306B<strong>\u660E\u308B\u3055\u306E\u4E0A\u9650</strong>\u3092\u6301\u305F\u305B\u3066\u3042\u308A\u3001\u30B0\u30ED\u30C3\u30B1\u30F3\u306A\u3089C5\u3088\u308A\u4E0A\u3067\u9CF4\u3089\u306A\u3044\u3068\u3053\u308D\u307E\u3067\u4E0B\u3052\u307E\u3059\u3002<strong>\u5019\u88DC\u304B\u3089\u5916\u3059\u306E\u3067\u306F\u306A\u304F\u7F6E\u304D\u5834\u6240\u3092\u5909\u3048\u307E\u3059</strong>\u2014\u2014\u5916\u3059\u3068\u97F3\u8272\u306E\u5E45\u304C\u305D\u306E\u3076\u3093\u6E1B\u308B\u3060\u3051\u3067\u3001\u4F4E\u304F\u9CF4\u3089\u3057\u305F\u30B0\u30ED\u30C3\u30B1\u30F3\u306F\u30DD\u30C3\u30D7\u30B9\u3067\u666E\u901A\u306B\u4F7F\u308F\u308C\u308B\u67D4\u3089\u304B\u3044\u97F3\u3067\u3059\u3002</p>
20730
+ <p><strong>\u30AA\u30AF\u30BF\u30FC\u30D6\u306E\u91CD\u306D\u3092\u4E3B\u5F79\u306B\u3057\u3066\u3044\u307E\u305B\u3093\u3002</strong>\u300C\u65E2\u306B\u3042\u308B\u30C8\u30E9\u30C3\u30AF\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u52D5\u304B\u3057\u3066\u5225\u30C8\u30E9\u30C3\u30AF\u3078\u5199\u3059\u300D\u5C64\u306F\u3001\u97F3\u697D\u7684\u306A\u4FA1\u5024\u304C\u9AD8\u304F\u3042\u308A\u307E\u305B\u3093\u3002\u4EBA\u306E\u8033\u306F\u30AA\u30AF\u30BF\u30FC\u30D6\u9055\u3044\u3092<strong>\u540C\u3058\u97F3</strong>\u3068\u3057\u3066\u805E\u304F\u306E\u3067\uFF08\u30AA\u30AF\u30BF\u30FC\u30D6\u7B49\u4FA1\uFF09\u3001\u5199\u3057\u305F\u5C64\u306F\u65B0\u3057\u3044\u58F0\u90E8\u306B\u306A\u3089\u305A\u3001\u97F3\u91CF\u3068\u97F3\u8272\u304C\u308F\u305A\u304B\u306B\u5909\u308F\u308B\u3060\u3051\u3067\u3059\u3002\u5F37\u8ABF\u3068\u3057\u3066\u306E\u610F\u5473\u306F\u3042\u308B\u306E\u3067\u4F7F\u3044\u306F\u3057\u307E\u3059\u304C\u3001\u5E38\u8A2D\u306B\u306F\u3057\u307E\u305B\u3093\u3002\u30D9\u30FC\u30B9\u306E\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\u306E\u91CD\u306D\u306F\u7279\u306B\u300130Hz\u524D\u5F8C\u307E\u3067\u843D\u3061\u3066\u8F2A\u90ED\u304C\u6FC1\u308B\u306E\u3067\u65E2\u5B9A\u3067\u306F\u51FA\u3057\u307E\u305B\u3093\uFF08\u51FA\u3059\u3068\u304D\u3082\u4E0A\u306E\u30AA\u30AF\u30BF\u30FC\u30D6\u3078\u3001\u76DB\u308A\u4E0A\u304C\u308B\u5834\u6240\u3060\u3051\u306B\u7F6E\u304D\u307E\u3059\uFF09\u3002</p>
20731
+ <p>\u300C\u4F5C\u66F2\u300D\u304C\u6C7A\u3081\u305F\u3053\u308C\u3089\u306E\u697D\u5668\u306F\u3001\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u306E\u5F79\u5272\u63A8\u5B9A\u3088\u308A\u512A\u5148\u3055\u308C\u307E\u3059\uFF08\u6F14\u594F\u5185\u5BB9\u3060\u3051\u3092\u898B\u308B\u3068\u3001\u9593\u594F\u306E\u30BD\u30ED\u3082\u30B5\u30D3\u306E\u91CD\u306D\u3082\u300C\u97F3\u306E\u5C11\u306A\u3044\u5358\u65CB\u5F8B\u300D\u3067\u3001\u4E3B\u65CB\u5F8B\u3068\u533A\u5225\u304C\u4ED8\u304B\u306A\u3044\u305F\u3081\u3067\u3059\uFF09\u3002\u697D\u5668\u3092\u624B\u3067\u9078\u3073\u76F4\u3057\u305F\u30C8\u30E9\u30C3\u30AF\u306F\u3001\u4EE5\u5F8C\u3069\u3061\u3089\u306B\u3082\u4E0A\u66F8\u304D\u3055\u308C\u307E\u305B\u3093\u3002</p>
19374
20732
  <h4>\u305D\u306E\u307B\u304B</h4>
19375
20733
  <ul>
19376
20734
  <li><strong>\u66F2\u306E\u9014\u4E2D\u3067\u8EE2\u8ABF\u3057\u307E\u3059</strong>\uFF08\u304A\u3088\u305D\u534A\u5206\u306E\u66F2\uFF09\u3002\u4E94\u5EA6\u570F\u3067\u8FD1\u3044\u5C5E\u8ABF\u30FB\u4E0B\u5C5E\u8ABF\u3078\u306F\u5171\u901A\u3059\u308B\u548C\u97F3\uFF08\u30D4\u30DC\u30C3\u30C8\u30B3\u30FC\u30C9\uFF09\u304B\u65B0\u3057\u3044\u8ABF\u306E\u30C9\u30DF\u30CA\u30F3\u30C8\u3067\u6A4B\u6E21\u3057\u3057\u3001\u30E9\u30B9\u30B5\u30D3\u306E\u534A\u97F3\u4E0A\u3052\u306F\u6E96\u5099\u306A\u3057\u306E\u76F4\u63A5\u8EE2\u8ABF\u306B\u3057\u307E\u3059\u3002\u8ABF\u53F7\u3092\u5909\u3048\u305A\u306B\u660E\u6697\u3060\u3051\u5165\u308C\u66FF\u3048\u308B<strong>\u5E73\u884C\u8ABF</strong>\uFF08\u30CF\u9577\u8ABF\u2194\u30A4\u77ED\u8ABF\uFF09\u3068\u3001\u4E3B\u97F3\u3092\u4FDD\u3063\u305F\u307E\u307E\u6697\u304F\u3059\u308B<strong>\u540C\u4E3B\u8ABF</strong>\uFF08\u30CF\u9577\u8ABF\u2192\u30CF\u77ED\u8ABF\uFF09\u3082\u5F15\u304D\u307E\u3059\u30021\u5272\u5F37\u306E\u66F2\u306F\u4E3B\u548C\u97F3\u3092\u907F\u3051\u3066\u300C\u660E\u308B\u3044\u306E\u304B\u6697\u3044\u306E\u304B\u5206\u304B\u3089\u306A\u3044\u300D\u6D6E\u904A\u611F\u3067\u901A\u3057\u307E\u3059\u3002</li>
@@ -19637,26 +20995,6 @@ var pickComposeVocal = (exclude) => {
19637
20995
  const list = pool.length > 0 ? pool : COMPOSE_VOCAL_POOL;
19638
20996
  return list[Math.floor(Math.random() * list.length)] ?? "klatt";
19639
20997
  };
19640
- var ADVANCED_COMPOSE_LAYOUT = [
19641
- { index: 0, part: "melody", octave: 0, volume: 104 },
19642
- { index: 1, part: "melody", octave: 1, volume: 62 },
19643
- { index: 2, part: "harmony", octave: 0, volume: 82 },
19644
- { index: 3, part: "submelody", octave: 0, volume: 86 },
19645
- { index: 4, part: "bass", octave: 0, volume: 92 },
19646
- { index: 5, part: "bass", octave: -1, volume: 58 },
19647
- { index: 6, part: "pad", octave: 0, volume: 64 },
19648
- { index: 7, part: "block", octave: 0, volume: 62 },
19649
- { index: 8, part: "arpeggio", octave: 0, volume: 54 },
19650
- { index: 9, part: "offbeat", octave: 0, volume: 50 },
19651
- { index: 10, part: "uwamono", octave: 1, volume: 56 },
19652
- // 掛け合い(デュエット)の相手。**歌入り作曲のときだけ**中身が入る。
19653
- // 作曲だけならメロディは t0 が全部持つので、ここは空のまま。
19654
- { index: 11, part: "duet", octave: 0, volume: 104 },
19655
- // 2声目のハモリ(主旋律を上下から挟む3声)と、主旋律のオクターブ下の重ね。
19656
- // どちらも曲ごとに出るかどうかが決まる(`song.vocal`)。
19657
- { index: 12, part: "harmony2", octave: 0, volume: 74 },
19658
- { index: 13, part: "melody", octave: -1, volume: 56 }
19659
- ];
19660
20998
  var LYRIC_MODEL_CATEGORIES = [
19661
20999
  {
19662
21000
  label: "kusa\u30D7\u30EA\u30BB\u30C3\u30C8",
@@ -19812,7 +21150,7 @@ var mountDAW = (target, options = {}) => {
19812
21150
  showChord,
19813
21151
  showMidiSearch,
19814
21152
  // 「作曲」は simple では役割固定の4トラックへ、advanced では15トラックへ
19815
- // 編曲を展開する({@link ADVANCED_COMPOSE_LAYOUT})。どちらでも出す。
21153
+ // 編曲を展開する({@link buildAdvancedLayers})。どちらでも出す。
19816
21154
  showCompose: true
19817
21155
  });
19818
21156
  refs.masterVolume.value = String(options.masterVolume ?? 50);
@@ -20006,6 +21344,14 @@ var mountDAW = (target, options = {}) => {
20006
21344
  while (customVocalsMap.has(`custom${n}`)) n++;
20007
21345
  return `custom${n}`;
20008
21346
  };
21347
+ const getVocalIconSrc = (lyricModel) => {
21348
+ if (!lyricModel) return void 0;
21349
+ const customDef = customVocalsMap.get(lyricModel);
21350
+ if (customDef !== void 0)
21351
+ return customDef.iconUrl || FALLBACK_VOCAL_ICON;
21352
+ const imgKey = VOICE_IMAGE_KEY[lyricModel.toLowerCase()];
21353
+ return imgKey ? VOICE_IMAGES[imgKey] : void 0;
21354
+ };
20009
21355
  let selectedNotes = [];
20010
21356
  let selectionRect = null;
20011
21357
  let copiedNotes = [];
@@ -20139,6 +21485,7 @@ var mountDAW = (target, options = {}) => {
20139
21485
  vocalTension: t1?.vocalTension ?? 50,
20140
21486
  vocalOctaveUnison: t1?.vocalOctaveUnison ?? "none",
20141
21487
  trackInstrument: t1?.trackInstrument ?? "",
21488
+ composeSlot: null,
20142
21489
  trackCompression: t1?.trackCompression ?? 0,
20143
21490
  trackWidth: t1?.trackWidth ?? 100,
20144
21491
  trackReverbSend: t1?.trackReverbSend ?? 0,
@@ -21190,25 +22537,38 @@ var mountDAW = (target, options = {}) => {
21190
22537
  refs.undoBtn.disabled = !core.canUndo();
21191
22538
  refs.redoBtn.disabled = !core.canRedo();
21192
22539
  };
21193
- const updateTrackPanel = () => {
22540
+ const updateTrackTabs = () => {
21194
22541
  refs.trackTabs.innerHTML = "";
21195
22542
  trackPillEls.clear();
21196
22543
  for (const [i2, t] of trackStates.entries()) {
21197
22544
  const [r, g, b] = t.config.color;
21198
22545
  const isActive = t.config.id === activeTrackId;
21199
22546
  const btn = document.createElement("button");
21200
- btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""}`;
22547
+ const vocalIconSrc = getVocalIconSrc(t.lyricModel);
22548
+ btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""} ${vocalIconSrc ? "dtm-pill--vocal" : ""}`;
21201
22549
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
22550
+ if (vocalIconSrc) {
22551
+ btn.style.setProperty(
22552
+ "--dtm-pill-icon",
22553
+ `url(${JSON.stringify(vocalIconSrc)})`
22554
+ );
22555
+ }
21202
22556
  btn.title = `Track ${i2 + 1}: ${t.config.name}`;
21203
22557
  btn.setAttribute(
21204
22558
  "aria-label",
21205
- `Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}`
22559
+ `Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}${vocalIconSrc ? "\uFF08\u30DC\u30FC\u30AB\u30EB\u9078\u629E\u4E2D\uFF09" : ""}`
21206
22560
  );
21207
- btn.textContent = String(i2 + 1);
22561
+ const labelEl = document.createElement("span");
22562
+ labelEl.className = "dtm-pill__label";
22563
+ labelEl.textContent = String(i2 + 1);
22564
+ btn.appendChild(labelEl);
21208
22565
  btn.addEventListener("click", () => switchTrack(t.config.id));
21209
22566
  refs.trackTabs.appendChild(btn);
21210
22567
  trackPillEls.set(t.config.id, btn);
21211
22568
  }
22569
+ };
22570
+ const updateTrackPanel = () => {
22571
+ updateTrackTabs();
21212
22572
  const active = getActive();
21213
22573
  const activeIndex = trackStates.findIndex(
21214
22574
  (t) => t.config.id === activeTrackId
@@ -21525,6 +22885,7 @@ var mountDAW = (target, options = {}) => {
21525
22885
  syncInstDisabled();
21526
22886
  instSel.addEventListener("change", () => {
21527
22887
  active.trackInstrument = instSel.value;
22888
+ active.composeSlot = null;
21528
22889
  const trackIndex = trackStates.indexOf(active);
21529
22890
  options.onTrackInstrumentChange?.(trackIndex, active.trackInstrument);
21530
22891
  persistTrack1(active);
@@ -21928,6 +23289,7 @@ var mountDAW = (target, options = {}) => {
21928
23289
  redrawAll();
21929
23290
  fireLyricsChange(active);
21930
23291
  reloadVoicesForModel(active.lyricModel);
23292
+ updateTrackTabs();
21931
23293
  });
21932
23294
  lyricCustomGuide.addEventListener("click", () => {
21933
23295
  showModal("\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0(.koe)\u306E\u4F7F\u3044\u65B9", KOE_INFO_HTML);
@@ -22461,6 +23823,7 @@ var mountDAW = (target, options = {}) => {
22461
23823
  }
22462
23824
  trackStates.forEach((t, i2) => {
22463
23825
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
23826
+ t.composeSlot = null;
22464
23827
  const name = normalizeInstrumentName(meta.trackInstruments?.[i2] ?? "");
22465
23828
  if (t.trackInstrument !== name) {
22466
23829
  t.trackInstrument = name;
@@ -22712,7 +24075,7 @@ var mountDAW = (target, options = {}) => {
22712
24075
  const p = programOfInstrumentName(t.trackInstrument);
22713
24076
  if (p !== null) program = p;
22714
24077
  } else if (!t.lyricModel) {
22715
- const role = DECLARED_ROLE[t.config.id] ?? "melody";
24078
+ const role = t.composeSlot ?? DECLARED_ROLE[t.config.id] ?? "melody";
22716
24079
  const instName = autoPreset[role] ?? autoPreset.melody;
22717
24080
  const p = programOfInstrumentName(instName);
22718
24081
  if (p !== null) program = p;
@@ -23081,7 +24444,7 @@ var mountDAW = (target, options = {}) => {
23081
24444
  if (!stats) continue;
23082
24445
  const role = DECLARED_ROLE[t.config.id] ?? classifyTrackRole(stats, t.config.id === topSingleVoiceId);
23083
24446
  roleByTrackId.set(t.config.id, role);
23084
- const instName = autoPreset[role];
24447
+ const instName = autoPreset[t.composeSlot ?? role] ?? autoPreset[role];
23085
24448
  t.trackInstrument = instName;
23086
24449
  const trackIndex = trackStates.indexOf(t);
23087
24450
  options.onTrackInstrumentChange?.(trackIndex, instName);
@@ -23232,6 +24595,15 @@ var mountDAW = (target, options = {}) => {
23232
24595
  refs.composeKey.value = savedKey;
23233
24596
  }
23234
24597
  }
24598
+ const savedScale = readMacroSetting("scale");
24599
+ if (savedScale && refs.composeScale) {
24600
+ const hasOption = Array.from(refs.composeScale.options).some(
24601
+ (opt) => opt.value === savedScale
24602
+ );
24603
+ if (hasOption) {
24604
+ refs.composeScale.value = savedScale;
24605
+ }
24606
+ }
23235
24607
  const savedShift = readMacroSetting("shift");
23236
24608
  if (savedShift && refs.shiftSelect) {
23237
24609
  const hasOption = Array.from(refs.shiftSelect.options).some(
@@ -23294,6 +24666,20 @@ var mountDAW = (target, options = {}) => {
23294
24666
  });
23295
24667
  updateComposeKeyHint();
23296
24668
  }
24669
+ const updateComposeScaleHint = () => {
24670
+ if (!refs.composeScale || !refs.composeScaleHint) return;
24671
+ const desc = getComposeScaleDescription(refs.composeScale.value);
24672
+ refs.composeScaleHint.textContent = desc;
24673
+ refs.composeScaleHint.title = desc;
24674
+ };
24675
+ const composeScale = refs.composeScale;
24676
+ if (composeScale) {
24677
+ composeScale.addEventListener("change", () => {
24678
+ writeMacroSetting("scale", composeScale.value);
24679
+ updateComposeScaleHint();
24680
+ });
24681
+ updateComposeScaleHint();
24682
+ }
23297
24683
  if (refs.shiftSelect) {
23298
24684
  refs.shiftSelect.addEventListener("change", () => {
23299
24685
  writeMacroSetting("shift", refs.shiftSelect.value);
@@ -23314,6 +24700,7 @@ var mountDAW = (target, options = {}) => {
23314
24700
  sections: selectedComposeSections(),
23315
24701
  template: tmpl,
23316
24702
  baseKey: refs.composeKey?.value ?? "any",
24703
+ scale: refs.composeScale?.value ?? "auto",
23317
24704
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
23318
24705
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
23319
24706
  recent: recentComposeFingerprints
@@ -23347,45 +24734,39 @@ var mountDAW = (target, options = {}) => {
23347
24734
  }
23348
24735
  track.core.endBatch();
23349
24736
  };
24737
+ const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
24738
+ if (shouldAutoInstrument && song.instrument) {
24739
+ currentInstrument = song.instrument;
24740
+ autoComposeInstrument = song.instrument;
24741
+ options.onInstrumentChange?.(song.instrument);
24742
+ }
23350
24743
  if (isAdvanced) {
23351
- for (const layer of ADVANCED_COMPOSE_LAYOUT) {
24744
+ const layers = buildAdvancedLayers(song, {
24745
+ edo: renderConfig.edo,
24746
+ stepsPerBar: renderConfig.stepsPerBar,
24747
+ preset: INSTRUMENT_PRESETS[currentInstrument] ?? INSTRUMENT_PRESETS.piano
24748
+ });
24749
+ for (const layer of layers) {
23352
24750
  const track = trackStates[layer.index];
23353
24751
  if (!track) continue;
23354
- const notes = layer.part === "duet" ? [] : layer.part === "harmony2" ? song.harmony2 : layer.index === 13 ? song.octave : 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({
23355
- edo: renderConfig.edo,
23356
- chordStr: song.chordProgression,
23357
- patternType: "arpeggio",
23358
- rootShift: song.rootShift,
23359
- bpm: song.bpm,
23360
- stepsPerBar: renderConfig.stepsPerBar
23361
- }).map((p) => ({
23362
- startStep: p.startStep,
23363
- pitchUnits: p.pitchUnits,
23364
- durationSteps: p.durationSteps,
23365
- velocity: Math.max(30, p.velocity - 14)
23366
- })) : buildChordPlacements({
23367
- edo: renderConfig.edo,
23368
- chordStr: song.chordProgression,
23369
- patternType: layer.part,
23370
- rootShift: song.rootShift,
23371
- bpm: song.bpm,
23372
- stepsPerBar: renderConfig.stepsPerBar
23373
- }).map((p) => ({
23374
- startStep: p.startStep,
23375
- pitchUnits: p.pitchUnits,
23376
- durationSteps: p.durationSteps,
23377
- velocity: p.velocity
23378
- }));
23379
- writeTrackAt(layer.index, notes);
24752
+ writeTrackAt(layer.index, layer.notes);
23380
24753
  track.trackOctave = layer.octave;
23381
24754
  track.volume = layer.volume;
23382
24755
  track.core.setVolume(layer.volume);
24756
+ track.composeSlot = layer.notes.length > 0 ? layer.slot ?? null : null;
24757
+ if (layer.notes.length === 0 && track.trackInstrument) {
24758
+ track.trackInstrument = "";
24759
+ options.onTrackInstrumentChange?.(layer.index, "");
24760
+ }
23383
24761
  }
23384
24762
  for (let i2 = 0; i2 < trackStates.length; i2++) {
23385
- if (ADVANCED_COMPOSE_LAYOUT.some((l) => l.index === i2)) continue;
24763
+ if (layers.some((l) => l.index === i2)) continue;
23386
24764
  writeTrackAt(i2, []);
24765
+ const t = trackStates[i2];
24766
+ if (t) t.composeSlot = null;
23387
24767
  }
23388
24768
  } else {
24769
+ for (const t of trackStates) t.composeSlot = null;
23389
24770
  writeTrack("melody", song.melody);
23390
24771
  writeTrack("submelody", song.submelody);
23391
24772
  writeTrack("bass", song.bass);
@@ -23402,12 +24783,6 @@ var mountDAW = (target, options = {}) => {
23402
24783
  refs.drumSelect.value = song.drum;
23403
24784
  options.onDrumChange?.(song.drum);
23404
24785
  applyDrumPatternFont(song.drum);
23405
- const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
23406
- if (shouldAutoInstrument && song.instrument) {
23407
- currentInstrument = song.instrument;
23408
- autoComposeInstrument = song.instrument;
23409
- options.onInstrumentChange?.(song.instrument);
23410
- }
23411
24786
  if (withVocal) {
23412
24787
  autoComposeVocalTracks.clear();
23413
24788
  const melodyTrack = isAdvanced ? trackStates[0] : trackStates.find((t) => t.config.id === "melody");
@@ -24644,6 +26019,7 @@ var mountDAW = (target, options = {}) => {
24644
26019
  if (!t) return;
24645
26020
  const name = normalizeInstrumentName(instrumentName);
24646
26021
  t.trackInstrument = name;
26022
+ t.composeSlot = null;
24647
26023
  if (t.config.id === activeTrackId) updateTrackPanel();
24648
26024
  },
24649
26025
  noteToCanvas: (step, pitch) => {
@@ -24767,8 +26143,9 @@ var playSingingMML = async (mml, options = {}) => {
24767
26143
  });
24768
26144
  const ownsCtx = !options.audioContext;
24769
26145
  const ctx = options.audioContext ?? new AudioContext();
24770
- const destination = options.destination ?? ctx.destination;
26146
+ const rawDestination = options.destination ?? ctx.destination;
24771
26147
  const useSynth = options.synth ?? !options.onPlayNote;
26148
+ const destination = createSafetyLimiter(ctx, rawDestination);
24772
26149
  const synth = useSynth ? createSynth(ctx, destination) : null;
24773
26150
  const pauseWhenHidden = options.pauseWhenHidden ?? ownsCtx;
24774
26151
  let playing = false;
@@ -25773,21 +27150,29 @@ var createDtmStudio = async (options = {}) => {
25773
27150
  };
25774
27151
  applyGlueCompression(options.masterCompression ?? 0);
25775
27152
  const setMasterCompression = (amount) => applyGlueCompression(amount);
25776
- const safetyLimiter = audioCtx.createDynamicsCompressor();
25777
- safetyLimiter.threshold.value = -1;
25778
- safetyLimiter.knee.value = 0;
25779
- safetyLimiter.ratio.value = 20;
25780
- safetyLimiter.attack.value = 1e-3;
25781
- safetyLimiter.release.value = 0.1;
25782
- glueMakeup.connect(safetyLimiter);
25783
27153
  const fadeGain = audioCtx.createGain();
25784
27154
  fadeGain.gain.value = 1;
25785
- safetyLimiter.connect(fadeGain);
25786
27155
  fadeGain.connect(options.destination ?? audioCtx.destination);
27156
+ const safetyLimiter = createSafetyLimiter(audioCtx, fadeGain);
27157
+ glueMakeup.connect(safetyLimiter);
27158
+ const FADE_RESTORE_SEC = 0.02;
27159
+ const rampFadeGainToUnity = (from, at) => {
27160
+ fadeGain.gain.setValueAtTime(from, at);
27161
+ fadeGain.gain.linearRampToValueAtTime(1, at + FADE_RESTORE_SEC);
27162
+ };
27163
+ const restoreFadeGainIfMuted = () => {
27164
+ const current = fadeGain.gain.value;
27165
+ if (current >= 1) return;
27166
+ const now = audioCtx.currentTime;
27167
+ fadeGain.gain.cancelScheduledValues(now);
27168
+ rampFadeGainToUnity(current, now);
27169
+ };
25787
27170
  const scheduleFade = (params) => {
25788
- fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
27171
+ const now = audioCtx.currentTime;
27172
+ const current = fadeGain.gain.value;
27173
+ fadeGain.gain.cancelScheduledValues(now);
25789
27174
  if (!params) {
25790
- fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
27175
+ rampFadeGainToUnity(current, now);
25791
27176
  return;
25792
27177
  }
25793
27178
  const { fadeInStartAt, fadeInEndAt, fadeOutStartAt, fadeOutEndAt } = params;
@@ -25795,13 +27180,12 @@ var createDtmStudio = async (options = {}) => {
25795
27180
  fadeGain.gain.setValueAtTime(0, fadeInStartAt);
25796
27181
  fadeGain.gain.linearRampToValueAtTime(1, fadeInEndAt);
25797
27182
  } else {
25798
- fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
27183
+ rampFadeGainToUnity(current, now);
25799
27184
  }
25800
27185
  if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
25801
27186
  fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
25802
27187
  fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
25803
- fadeGain.gain.setValueAtTime(0, fadeOutEndAt + 2);
25804
- fadeGain.gain.setValueAtTime(1, fadeOutEndAt + 2);
27188
+ rampFadeGainToUnity(0, fadeOutEndAt + 2);
25805
27189
  }
25806
27190
  };
25807
27191
  const clipMeter = createClipMeter(audioCtx, glueMakeup);
@@ -25861,8 +27245,7 @@ var createDtmStudio = async (options = {}) => {
25861
27245
  };
25862
27246
  };
25863
27247
  const resumeAudio = () => {
25864
- fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
25865
- fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
27248
+ restoreFadeGainIfMuted();
25866
27249
  if (audioCtx.state === "closed") return Promise.resolve();
25867
27250
  return audioCtx.resume();
25868
27251
  };
@@ -26073,12 +27456,6 @@ var createDtmStudio = async (options = {}) => {
26073
27456
  await listReady;
26074
27457
  nameToKey = await buildNameToKeyMapping();
26075
27458
  await Promise.all([drumReady, loadPreset(defaultPreset)]);
26076
- const restoreFadeGainIfMuted = () => {
26077
- if (fadeGain.gain.value < 1) {
26078
- fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
26079
- fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
26080
- }
26081
- };
26082
27459
  const playDrum = (e) => {
26083
27460
  if (!sfDrum.font) return;
26084
27461
  if (e.when === 0) restoreFadeGainIfMuted();
@@ -26829,7 +28206,7 @@ var createDtmStudio = async (options = {}) => {
26829
28206
  };
26830
28207
  const setMasterVolume = (volume) => {
26831
28208
  const g = Math.max(0, Math.min(100, volume)) / 100;
26832
- masterGain.gain.setValueAtTime(g, audioCtx.currentTime);
28209
+ masterGain.gain.setTargetAtTime(g, audioCtx.currentTime, 0.02);
26833
28210
  };
26834
28211
  let recordDestNode = null;
26835
28212
  const createMediaStreamDestination = () => {
@@ -26911,10 +28288,13 @@ var createDtmStudio = async (options = {}) => {
26911
28288
  export {
26912
28289
  A4_HZ,
26913
28290
  A4_UNITS,
28291
+ BLUES_SCALE,
26914
28292
  BREATH_MARK,
26915
28293
  CENTS_PER_UNIT,
26916
28294
  COMPOSE_KEYS,
26917
28295
  COMPOSE_MOOD_GROUPS,
28296
+ COMPOSE_SCALES,
28297
+ COMPOSE_SCALE_IDS,
26918
28298
  CORPUS_BANDS,
26919
28299
  CORPUS_SIZE,
26920
28300
  DAW_CSS,
@@ -26934,7 +28314,11 @@ export {
26934
28314
  FADE_IN_MARK,
26935
28315
  FADE_OUT_MARK,
26936
28316
  GLOBAL_STORAGE_KEYS,
28317
+ GM_BRIGHT_CEILING,
26937
28318
  GM_INSTRUMENT_NAMES,
28319
+ GM_INSTRUMENT_RANGE,
28320
+ HARMONIC_MINOR_SCALE,
28321
+ HUNGARIAN_SCALE,
26938
28322
  INSTRUMENT_PRESETS,
26939
28323
  KEY_COUNT,
26940
28324
  KOE_BASE_URL,
@@ -26944,6 +28328,7 @@ export {
26944
28328
  LinkedList,
26945
28329
  MACRO_STORAGE_KEYS,
26946
28330
  MAJOR_KEY_IDS,
28331
+ MAJOR_SCALE2 as MAJOR_SCALE,
26947
28332
  MAX_VOCAL_VOLUME,
26948
28333
  MICRO_STEP,
26949
28334
  MINOR_KEY_IDS,
@@ -27012,10 +28397,12 @@ export {
27012
28397
  featureVector,
27013
28398
  fifthToStep,
27014
28399
  fifthToUnits,
28400
+ fitInstrumentOctave,
27015
28401
  formatMmlMeta,
27016
28402
  freqFromPitch,
27017
28403
  generateRandomPattern,
27018
28404
  getComposeKeyDescription,
28405
+ getComposeScaleDescription,
27019
28406
  getDrumPatternKeys,
27020
28407
  getMidiBPM,
27021
28408
  icon,
@@ -27051,9 +28438,13 @@ export {
27051
28438
  readGlobalSetting,
27052
28439
  readMacroSections,
27053
28440
  readMacroSetting,
28441
+ resolveCenter,
27054
28442
  resolveComposeKey,
28443
+ resolveComposeScale,
27055
28444
  resolveDrumPattern,
27056
28445
  resolveLoopPoint,
28446
+ scaleDegrees,
28447
+ scaleSize,
27057
28448
  semitonesToUnits,
27058
28449
  shiftNotes,
27059
28450
  showLoadingOverlay,