@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.js CHANGED
@@ -22,10 +22,13 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  A4_HZ: () => A4_HZ,
24
24
  A4_UNITS: () => A4_UNITS,
25
+ BLUES_SCALE: () => BLUES_SCALE,
25
26
  BREATH_MARK: () => BREATH_MARK,
26
27
  CENTS_PER_UNIT: () => CENTS_PER_UNIT,
27
28
  COMPOSE_KEYS: () => COMPOSE_KEYS,
28
29
  COMPOSE_MOOD_GROUPS: () => COMPOSE_MOOD_GROUPS,
30
+ COMPOSE_SCALES: () => COMPOSE_SCALES,
31
+ COMPOSE_SCALE_IDS: () => COMPOSE_SCALE_IDS,
29
32
  CORPUS_BANDS: () => CORPUS_BANDS,
30
33
  CORPUS_SIZE: () => CORPUS_SIZE,
31
34
  DAW_CSS: () => DAW_CSS,
@@ -45,7 +48,11 @@ __export(index_exports, {
45
48
  FADE_IN_MARK: () => FADE_IN_MARK,
46
49
  FADE_OUT_MARK: () => FADE_OUT_MARK,
47
50
  GLOBAL_STORAGE_KEYS: () => GLOBAL_STORAGE_KEYS,
51
+ GM_BRIGHT_CEILING: () => GM_BRIGHT_CEILING,
48
52
  GM_INSTRUMENT_NAMES: () => GM_INSTRUMENT_NAMES,
53
+ GM_INSTRUMENT_RANGE: () => GM_INSTRUMENT_RANGE,
54
+ HARMONIC_MINOR_SCALE: () => HARMONIC_MINOR_SCALE,
55
+ HUNGARIAN_SCALE: () => HUNGARIAN_SCALE,
49
56
  INSTRUMENT_PRESETS: () => INSTRUMENT_PRESETS,
50
57
  KEY_COUNT: () => KEY_COUNT,
51
58
  KOE_BASE_URL: () => KOE_BASE_URL,
@@ -55,6 +62,7 @@ __export(index_exports, {
55
62
  LinkedList: () => LinkedList,
56
63
  MACRO_STORAGE_KEYS: () => MACRO_STORAGE_KEYS,
57
64
  MAJOR_KEY_IDS: () => MAJOR_KEY_IDS,
65
+ MAJOR_SCALE: () => MAJOR_SCALE2,
58
66
  MAX_VOCAL_VOLUME: () => MAX_VOCAL_VOLUME,
59
67
  MICRO_STEP: () => MICRO_STEP,
60
68
  MINOR_KEY_IDS: () => MINOR_KEY_IDS,
@@ -123,10 +131,12 @@ __export(index_exports, {
123
131
  featureVector: () => featureVector,
124
132
  fifthToStep: () => fifthToStep,
125
133
  fifthToUnits: () => fifthToUnits,
134
+ fitInstrumentOctave: () => fitInstrumentOctave,
126
135
  formatMmlMeta: () => formatMmlMeta,
127
136
  freqFromPitch: () => freqFromPitch,
128
137
  generateRandomPattern: () => generateRandomPattern,
129
138
  getComposeKeyDescription: () => getComposeKeyDescription,
139
+ getComposeScaleDescription: () => getComposeScaleDescription,
130
140
  getDrumPatternKeys: () => getDrumPatternKeys,
131
141
  getMidiBPM: () => getMidiBPM,
132
142
  icon: () => icon,
@@ -162,9 +172,13 @@ __export(index_exports, {
162
172
  readGlobalSetting: () => readGlobalSetting,
163
173
  readMacroSections: () => readMacroSections,
164
174
  readMacroSetting: () => readMacroSetting,
175
+ resolveCenter: () => resolveCenter,
165
176
  resolveComposeKey: () => resolveComposeKey,
177
+ resolveComposeScale: () => resolveComposeScale,
166
178
  resolveDrumPattern: () => resolveDrumPattern,
167
179
  resolveLoopPoint: () => resolveLoopPoint,
180
+ scaleDegrees: () => scaleDegrees,
181
+ scaleSize: () => scaleSize,
168
182
  semitonesToUnits: () => semitonesToUnits,
169
183
  shiftNotes: () => shiftNotes,
170
184
  showLoadingOverlay: () => showLoadingOverlay,
@@ -1956,11 +1970,19 @@ var createChannelStrip = (ctx, destination, options = {}) => {
1956
1970
  const duckGain = ctx.createGain();
1957
1971
  duckGain.gain.value = 1;
1958
1972
  const DUCK_ATTACK_SEC = 8e-3;
1973
+ let duckReleaseEndAt = 0;
1959
1974
  const duck = (atTime, depth = 0.3, releaseSec = 0.18) => {
1960
1975
  const t = Math.max(atTime, ctx.currentTime);
1961
1976
  const floor = clamp(1 - depth, 0, 1);
1962
- duckGain.gain.linearRampToValueAtTime(floor, t + DUCK_ATTACK_SEC);
1963
- duckGain.gain.linearRampToValueAtTime(1, t + DUCK_ATTACK_SEC + releaseSec);
1977
+ const param = duckGain.gain;
1978
+ if (t >= duckReleaseEndAt) {
1979
+ param.setValueAtTime(1, t);
1980
+ } else if (typeof param.cancelAndHoldAtTime === "function") {
1981
+ param.cancelAndHoldAtTime(t);
1982
+ }
1983
+ param.linearRampToValueAtTime(floor, t + DUCK_ATTACK_SEC);
1984
+ param.linearRampToValueAtTime(1, t + DUCK_ATTACK_SEC + releaseSec);
1985
+ duckReleaseEndAt = t + DUCK_ATTACK_SEC + releaseSec;
1964
1986
  };
1965
1987
  eqHigh.connect(compressor);
1966
1988
  compressor.connect(duckGain);
@@ -2257,12 +2279,15 @@ var buildChordPlacements = (options) => {
2257
2279
  }
2258
2280
  }
2259
2281
  } else if (patternType === "alternating") {
2282
+ const quarterSteps = Math.floor(stepsPerBar / 4);
2260
2283
  notes.forEach((noteOffset, i2) => {
2261
- const stepOffset = i2 * Math.floor(stepsPerBar / 4);
2284
+ const stepOffset = i2 * quarterSteps;
2285
+ const isLast = i2 === notes.length - 1;
2286
+ const durationSteps = isLast ? Math.max(12, noteLength - stepOffset) : Math.max(12, quarterSteps);
2262
2287
  placements.push({
2263
2288
  startStep: chord.whenStep + stepOffset,
2264
2289
  pitchUnits: toUnits(noteOffset),
2265
- durationSteps: Math.max(12, Math.floor(stepsPerBar / 4)),
2290
+ durationSteps,
2266
2291
  velocity: 100
2267
2292
  });
2268
2293
  });
@@ -4890,6 +4915,45 @@ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
4890
4915
  };
4891
4916
  var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100;
4892
4917
 
4918
+ // src/safety-limiter.ts
4919
+ var THRESHOLD_DB = -1;
4920
+ var KNEE_DB = 0;
4921
+ var RATIO = 20;
4922
+ var ATTACK_SEC = 1e-3;
4923
+ var RELEASE_SEC = 0.1;
4924
+ var CURVE_SAMPLES2 = 2048;
4925
+ var SOFT_KNEE = 0.85;
4926
+ var CEILING = 0.98;
4927
+ var SHAPER_RANGE = 4;
4928
+ var OVERSAMPLE2 = "none";
4929
+ var createSoftClipCurve = () => {
4930
+ const curve = new Float32Array(CURVE_SAMPLES2);
4931
+ const span = CEILING - SOFT_KNEE;
4932
+ for (let i2 = 0; i2 < CURVE_SAMPLES2; i2++) {
4933
+ const x2 = (i2 / (CURVE_SAMPLES2 - 1) * 2 - 1) * SHAPER_RANGE;
4934
+ const a = Math.abs(x2);
4935
+ curve[i2] = Math.sign(x2) * (a <= SOFT_KNEE ? a : SOFT_KNEE + span * Math.tanh((a - SOFT_KNEE) / span));
4936
+ }
4937
+ return curve;
4938
+ };
4939
+ var createSafetyLimiter = (ctx, destination) => {
4940
+ const limiter = ctx.createDynamicsCompressor();
4941
+ limiter.threshold.value = THRESHOLD_DB;
4942
+ limiter.knee.value = KNEE_DB;
4943
+ limiter.ratio.value = RATIO;
4944
+ limiter.attack.value = ATTACK_SEC;
4945
+ limiter.release.value = RELEASE_SEC;
4946
+ const preScale = ctx.createGain();
4947
+ preScale.gain.value = 1 / SHAPER_RANGE;
4948
+ const softClip = ctx.createWaveShaper();
4949
+ softClip.curve = createSoftClipCurve();
4950
+ softClip.oversample = OVERSAMPLE2;
4951
+ limiter.connect(preScale);
4952
+ preScale.connect(softClip);
4953
+ softClip.connect(destination);
4954
+ return limiter;
4955
+ };
4956
+
4893
4957
  // src/sequencer.ts
4894
4958
  var STEPS_PER_BEAT = 48;
4895
4959
  var PLAN_TIME = 0.5;
@@ -7353,6 +7417,9 @@ var SONG_DRUM_PATTERNS = {
7353
7417
 
7354
7418
  // src/synth.ts
7355
7419
  var freqFromPitch = (pitchUnits) => unitsToHz(pitchUnits);
7420
+ var MIN_ATTACK_SEC = 15e-4;
7421
+ var MIN_RELEASE_SEC = 4e-3;
7422
+ var TAIL_RATIO = 1e-3;
7356
7423
  var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7357
7424
  const wave = tone.wave ?? "square";
7358
7425
  const attack = tone.attack ?? 0;
@@ -7371,25 +7438,25 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7371
7438
  osc.frequency.value = freqFromPitch(e.pitchUnits);
7372
7439
  const t0 = ctx.currentTime + e.when;
7373
7440
  const peak = Math.max(1e-4, 0.06 * e.volume * 1.5 * gainScale);
7441
+ const tail = peak * TAIL_RATIO;
7442
+ const stopAt = t0 + e.duration + MIN_RELEASE_SEC;
7374
7443
  if (tone.decay) {
7375
7444
  gain.gain.setValueAtTime(1e-4, t0);
7376
7445
  gain.gain.linearRampToValueAtTime(peak, t0 + Math.max(3e-3, attack));
7377
- gain.gain.exponentialRampToValueAtTime(1e-3, t0 + e.duration);
7446
+ gain.gain.exponentialRampToValueAtTime(tail, t0 + e.duration);
7378
7447
  } else {
7379
7448
  const releaseTime = Math.min(0.02, e.duration * 0.1);
7380
7449
  const sustainDuration = e.duration - releaseTime;
7381
- if (attack > 0) {
7382
- gain.gain.setValueAtTime(1e-4, t0);
7383
- gain.gain.linearRampToValueAtTime(
7384
- peak,
7385
- t0 + Math.min(attack, sustainDuration)
7386
- );
7387
- } else {
7388
- gain.gain.setValueAtTime(peak, t0);
7389
- }
7450
+ const attackTime = Math.min(
7451
+ Math.max(attack, MIN_ATTACK_SEC),
7452
+ sustainDuration
7453
+ );
7454
+ gain.gain.setValueAtTime(1e-4, t0);
7455
+ gain.gain.linearRampToValueAtTime(peak, t0 + attackTime);
7390
7456
  gain.gain.setValueAtTime(peak, t0 + sustainDuration);
7391
- gain.gain.exponentialRampToValueAtTime(1e-3, t0 + e.duration);
7457
+ gain.gain.exponentialRampToValueAtTime(tail, t0 + e.duration);
7392
7458
  }
7459
+ gain.gain.linearRampToValueAtTime(0, stopAt);
7393
7460
  osc.connect(gain);
7394
7461
  let panner = null;
7395
7462
  if (typeof ctx.createStereoPanner === "function" && e.pan) {
@@ -7401,7 +7468,7 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7401
7468
  gain.connect(compressor);
7402
7469
  }
7403
7470
  osc.start(t0);
7404
- osc.stop(t0 + e.duration + 0.02);
7471
+ osc.stop(stopAt);
7405
7472
  osc.onended = () => {
7406
7473
  osc.disconnect();
7407
7474
  gain.disconnect();
@@ -7416,14 +7483,21 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7416
7483
  if (isKick) {
7417
7484
  const osc = ctx.createOscillator();
7418
7485
  const g2 = ctx.createGain();
7486
+ const kickPeak = vol * 0.135;
7487
+ const kickDecayEnd = t0 + 0.18;
7488
+ const kickEnd = kickDecayEnd + MIN_RELEASE_SEC;
7419
7489
  osc.frequency.setValueAtTime(150, t0);
7420
7490
  osc.frequency.exponentialRampToValueAtTime(50, t0 + 0.12);
7421
- g2.gain.setValueAtTime(vol * 0.135, t0);
7422
- g2.gain.exponentialRampToValueAtTime(1e-3, t0 + 0.18);
7491
+ g2.gain.setValueAtTime(kickPeak, t0);
7492
+ g2.gain.exponentialRampToValueAtTime(kickPeak * TAIL_RATIO, kickDecayEnd);
7493
+ g2.gain.linearRampToValueAtTime(0, kickEnd);
7423
7494
  osc.connect(g2).connect(compressor);
7424
7495
  osc.start(t0);
7425
- osc.stop(t0 + 0.2);
7426
- osc.onended = () => osc.disconnect();
7496
+ osc.stop(kickEnd);
7497
+ osc.onended = () => {
7498
+ osc.disconnect();
7499
+ g2.disconnect();
7500
+ };
7427
7501
  return;
7428
7502
  }
7429
7503
  const dur = isSnareLike ? 0.18 : 0.05;
@@ -7437,8 +7511,13 @@ var createSynth = (ctx, destination = ctx.destination, tone = {}) => {
7437
7511
  filter.type = isSnareLike ? "bandpass" : "highpass";
7438
7512
  filter.frequency.value = isSnareLike ? 2e3 : 8e3;
7439
7513
  const g = ctx.createGain();
7440
- g.gain.setValueAtTime(vol * (isSnareLike ? 0.105 : 0.06), t0);
7441
- g.gain.exponentialRampToValueAtTime(1e-3, t0 + dur);
7514
+ const noisePeak = vol * (isSnareLike ? 0.105 : 0.06);
7515
+ g.gain.setValueAtTime(noisePeak, t0);
7516
+ g.gain.exponentialRampToValueAtTime(
7517
+ noisePeak * TAIL_RATIO,
7518
+ t0 + dur - MIN_RELEASE_SEC
7519
+ );
7520
+ g.gain.linearRampToValueAtTime(0, t0 + dur);
7442
7521
  src.connect(filter).connect(g).connect(compressor);
7443
7522
  src.start(t0);
7444
7523
  src.stop(t0 + dur);
@@ -7500,7 +7579,7 @@ var playPlacements = (placements, options) => {
7500
7579
  reverbPreDelay.connect(reverbConvolver);
7501
7580
  reverbConvolver.connect(reverbWetGain);
7502
7581
  reverbWetGain.connect(finalMix);
7503
- finalMix.connect(rawDestination);
7582
+ finalMix.connect(createSafetyLimiter(ctx, rawDestination));
7504
7583
  const channelStrips = /* @__PURE__ */ new Map();
7505
7584
  const getChannelStrip = (index) => {
7506
7585
  let strip = channelStrips.get(index);
@@ -8314,6 +8393,22 @@ var DAW_CSS = `
8314
8393
  opacity: 0.85;
8315
8394
  }
8316
8395
  .dtm-pill:not(.dtm-pill--active):active { transform: translate(1px,1px); box-shadow: none; }
8396
+ /* \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 */
8397
+ .dtm-pill--vocal::after {
8398
+ content: '';
8399
+ position: absolute;
8400
+ inset: 0;
8401
+ background-image: var(--dtm-pill-icon);
8402
+ background-size: contain;
8403
+ background-repeat: no-repeat;
8404
+ background-position: right center;
8405
+ opacity: 0.4;
8406
+ pointer-events: none;
8407
+ }
8408
+ .dtm-pill__label {
8409
+ position: relative;
8410
+ z-index: 1;
8411
+ }
8317
8412
  /* \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 */
8318
8413
  .dtm-pill--sounding {
8319
8414
  filter: brightness(1.6);
@@ -9939,17 +10034,24 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9939
10034
  if (!audioCtx) audioCtx = new AudioContext();
9940
10035
  return audioCtx;
9941
10036
  };
10037
+ let limiterNode = null;
10038
+ const ensureOutput = () => {
10039
+ const ctx = ensureCtx();
10040
+ if (!limiterNode) limiterNode = createSafetyLimiter(ctx, ctx.destination);
10041
+ return limiterNode;
10042
+ };
9942
10043
  let synthInstance = null;
9943
10044
  const ensureSynth = () => {
9944
- if (!synthInstance) synthInstance = createSynth(ensureCtx());
10045
+ if (!synthInstance) {
10046
+ synthInstance = createSynth(ensureCtx(), ensureOutput());
10047
+ }
9945
10048
  return synthInstance;
9946
10049
  };
9947
10050
  let voices = null;
9948
10051
  const ensureVoices = () => {
9949
10052
  if (options.singingVoices) return options.singingVoices;
9950
10053
  if (!voices) {
9951
- const ctx = ensureCtx();
9952
- voices = createSingingVoices(ctx, ctx.destination);
10054
+ voices = createSingingVoices(ensureCtx(), ensureOutput());
9953
10055
  voices.setVolume(trackVolume / 100 * (masterVolume / 100));
9954
10056
  }
9955
10057
  return voices;
@@ -11011,6 +11113,12 @@ var SoundFont = class _SoundFont {
11011
11113
  // ファミリごとに変える。
11012
11114
  /** アタック(無音からピークまで)秒。クリック防止の最小限。全楽器共通。 */
11013
11115
  static attackSec = 5e-3;
11116
+ /**
11117
+ * 消え際に最低限確保する秒数。アタックと対になるクリック防止で、**サンプルが
11118
+ * 終わるまでに**必ずここまでに 0 へ落とし切る。振幅が残ったままバッファが尽きたり
11119
+ * `stop()` が来たりすると、その瞬間の値がそのまま段差になりプチノイズが出る。
11120
+ */
11121
+ static minReleaseSec = 4e-3;
11014
11122
  /**
11015
11123
  * 減衰の型ごとのエンベロープ。
11016
11124
  *
@@ -11197,14 +11305,23 @@ var SoundFont = class _SoundFont {
11197
11305
  const startGainTime = Math.max(ctx.currentTime, _when);
11198
11306
  g.gain.setValueAtTime(0, startGainTime);
11199
11307
  const env = _SoundFont.envelopes[this.style.env] ?? _SoundFont.envelopes.sustain;
11200
- const limit = src.loop ? Number.POSITIVE_INFINITY : _when + _param.max - startOffsetSec;
11308
+ const playRate = _param.playbackRate * 2 ** ((humanizeCents + detuneCents) / 1200);
11309
+ const sampleEnd = _when + (buffer.duration - startOffsetSec) / playRate;
11310
+ const limit = src.loop ? Number.POSITIVE_INFINITY : sampleEnd;
11201
11311
  const attackEnd = Math.min(startGainTime + _SoundFont.attackSec, limit);
11202
11312
  const decayEnd = Math.min(
11203
11313
  Math.max(attackEnd, attackEnd + env.decaySec),
11204
11314
  limit
11205
11315
  );
11206
- const noteOff = Math.min(Math.max(decayEnd, _when + duration), limit);
11207
- const end = isDrum ? _when + buffer.duration - startOffsetSec : Math.max(Math.min(noteOff + env.releaseSec, limit), noteOff + 1e-3);
11316
+ const tailRoom = Math.min(
11317
+ _SoundFont.minReleaseSec,
11318
+ Math.max(0, limit - decayEnd)
11319
+ );
11320
+ const noteOff = Math.min(
11321
+ Math.max(decayEnd, _when + duration),
11322
+ limit - tailRoom
11323
+ );
11324
+ const end = isDrum ? sampleEnd : Math.max(Math.min(noteOff + env.releaseSec, limit), noteOff + tailRoom);
11208
11325
  if (!isDrum) {
11209
11326
  const sustainVolume = effectiveVolume * env.sustain;
11210
11327
  g.gain.linearRampToValueAtTime(effectiveVolume, attackEnd);
@@ -11213,6 +11330,11 @@ var SoundFont = class _SoundFont {
11213
11330
  g.gain.linearRampToValueAtTime(0, end);
11214
11331
  } else {
11215
11332
  g.gain.linearRampToValueAtTime(effectiveVolume, attackEnd);
11333
+ const releaseStart = Math.max(attackEnd, end - _SoundFont.minReleaseSec);
11334
+ if (releaseStart > attackEnd) {
11335
+ g.gain.setValueAtTime(effectiveVolume, releaseStart);
11336
+ }
11337
+ g.gain.linearRampToValueAtTime(0, end);
11216
11338
  }
11217
11339
  if (filter) src.connect(filter).connect(g);
11218
11340
  else src.connect(g);
@@ -11422,12 +11544,10 @@ var addParam = (zone, pitch) => {
11422
11544
  coarseTune,
11423
11545
  fineTune,
11424
11546
  sampleRate,
11425
- delay,
11426
- buffer
11547
+ delay
11427
11548
  } = zone;
11428
11549
  const baseDetune = originalPitch - 100 * coarseTune - fineTune;
11429
11550
  const playbackRate = 2 ** ((100 * pitch - baseDetune) / 1200);
11430
- const max = (buffer?.duration ?? 0) / playbackRate;
11431
11551
  const src = {
11432
11552
  loop: loopStart >= 1 && loopStart < loopEnd
11433
11553
  };
@@ -11435,7 +11555,7 @@ var addParam = (zone, pitch) => {
11435
11555
  [src.loopStart, src.loopEnd] = [loopStart, loopEnd].map(
11436
11556
  (v) => v / sampleRate + delay
11437
11557
  );
11438
- zone._param = { playbackRate, max, src };
11558
+ zone._param = { playbackRate, src };
11439
11559
  };
11440
11560
 
11441
11561
  // src/chord-player.ts
@@ -12733,29 +12853,30 @@ var CORPUS_BANDS = {
12733
12853
  climaxPeaks: [1, 2, 13.5, 26],
12734
12854
  complementarity: [0, 0.026, 0.179, 0.405]
12735
12855
  };
12736
- var CORPUS_MEDIANS = {
12737
- entropy: 0.907,
12738
- valueKinds: 4,
12739
- restRatio: 0.149,
12740
- leapRatio: 0.339,
12741
- stepRatio: 0.538,
12742
- chromaticRatio: 0.052,
12743
- maxLeap: 12,
12744
- melodyRange: 20,
12745
- notesPerBar: 5.511,
12746
- shortNoteRatio: 0.816,
12747
- barDensityCv: 0.279,
12748
- densityCliff: 0.026,
12749
- sim1: 0.518,
12750
- sim2: 0.552,
12751
- sim4: 0.616,
12752
- sim8: 0.676,
12753
- phraseBreath: 0.227,
12754
- turnRatio: 0.519,
12755
- climaxPosition: 0.273,
12756
- climaxPeaks: 6,
12757
- complementarity: 0.075
12758
- };
12856
+ var CORPUS_PROFILE_KEYS = [
12857
+ "entropy",
12858
+ "valueKinds",
12859
+ "restRatio",
12860
+ "leapRatio",
12861
+ "stepRatio",
12862
+ "chromaticRatio",
12863
+ "maxLeap",
12864
+ "melodyRange",
12865
+ "notesPerBar",
12866
+ "shortNoteRatio",
12867
+ "barDensityCv",
12868
+ "densityCliff",
12869
+ "sim1",
12870
+ "sim2",
12871
+ "sim4",
12872
+ "sim8",
12873
+ "phraseBreath",
12874
+ "turnRatio",
12875
+ "climaxPosition",
12876
+ "climaxPeaks",
12877
+ "complementarity"
12878
+ ];
12879
+ var CORPUS_DEVIATION_BUDGET = 4;
12759
12880
  var CORPUS_CELL_WEIGHTS = {
12760
12881
  "0,2,4,6,8,10,12,14": 584,
12761
12882
  "0,2,4,6,8,12,14": 512,
@@ -13620,16 +13741,15 @@ var tensionFeatures = (barTension, opts) => {
13620
13741
  return { rise, resolve };
13621
13742
  };
13622
13743
  var band = (v, lo, idealLo, idealHi, hi) => {
13623
- if (v <= lo || v >= hi) return 0;
13624
13744
  if (v >= idealLo && v <= idealHi) return 1;
13745
+ if (v <= lo || v >= hi) return 0;
13625
13746
  if (v < idealLo) return idealLo === lo ? 0 : (v - lo) / (idealLo - lo);
13626
13747
  return hi === idealHi ? 0 : (hi - v) / (hi - idealHi);
13627
13748
  };
13628
- var centeredBand = (v, b, median) => {
13629
- const score = band(v, b[0], b[1], b[2], b[3]);
13630
- if (score < 1) return score;
13631
- const spread = Math.max(median - b[1], b[2] - median, 1e-9);
13632
- return 1 - 0.15 * Math.min(1, Math.abs(v - median) / spread);
13749
+ var plausibleBand = (v, b) => {
13750
+ const span = b[3] - b[0];
13751
+ const margin = span > 0 ? span * 0.5 : 1;
13752
+ return band(v, b[0] - margin, b[0], b[3], b[3] + margin);
13633
13753
  };
13634
13754
  var featureVector = (f) => [
13635
13755
  f.entropy / 3,
@@ -13653,6 +13773,650 @@ var featureDistance = (a, b) => {
13653
13773
  return Math.sqrt(sum);
13654
13774
  };
13655
13775
 
13776
+ // src/compose-scales.ts
13777
+ var MAJOR_SCALE2 = [
13778
+ { semi: 0, fifth: 0 },
13779
+ // C
13780
+ { semi: 2, fifth: 2 },
13781
+ // D
13782
+ { semi: 4, fifth: 4 },
13783
+ // E
13784
+ { semi: 5, fifth: -1 },
13785
+ // F
13786
+ { semi: 7, fifth: 1 },
13787
+ // G
13788
+ { semi: 9, fifth: 3 },
13789
+ // A
13790
+ { semi: 11, fifth: 5 }
13791
+ // B
13792
+ ];
13793
+ var HARMONIC_MINOR_SCALE = [
13794
+ { semi: 0, fifth: 0 },
13795
+ // C
13796
+ { semi: 2, fifth: 2 },
13797
+ // D
13798
+ { semi: 4, fifth: 4 },
13799
+ // E
13800
+ { semi: 5, fifth: -1 },
13801
+ // F
13802
+ { semi: 8, fifth: 8 },
13803
+ // G#
13804
+ { semi: 9, fifth: 3 },
13805
+ // A
13806
+ { semi: 11, fifth: 5 }
13807
+ // B
13808
+ ];
13809
+ var HUNGARIAN_SCALE = [
13810
+ { semi: 0, fifth: 0 },
13811
+ // C
13812
+ { semi: 3, fifth: 9 },
13813
+ // D#
13814
+ { semi: 4, fifth: 4 },
13815
+ // E
13816
+ { semi: 5, fifth: -1 },
13817
+ // F
13818
+ { semi: 8, fifth: 8 },
13819
+ // G#
13820
+ { semi: 9, fifth: 3 },
13821
+ // A
13822
+ { semi: 11, fifth: 5 }
13823
+ // B
13824
+ ];
13825
+ var BLUES_SCALE = [
13826
+ { semi: 0, fifth: 0 },
13827
+ // C
13828
+ { semi: 3, fifth: -3 },
13829
+ // Eb
13830
+ { semi: 5, fifth: -1 },
13831
+ // F
13832
+ { semi: 6, fifth: -6 },
13833
+ // Gb(ブルーノート)
13834
+ { semi: 7, fifth: 1 },
13835
+ // G
13836
+ { semi: 10, fifth: -2 }
13837
+ // Bb
13838
+ ];
13839
+ var RYUKYU_CENTER = {
13840
+ tonic: "C",
13841
+ half: "G",
13842
+ deceptive: "Em",
13843
+ tonicPattern: /^C(?![#b]|m)/,
13844
+ a: [
13845
+ ["C", "F", "G", "C"],
13846
+ ["C", "C", "F", "G"],
13847
+ ["F", "G", "C", "C"],
13848
+ ["C", "G", "F", "G"],
13849
+ ["CM7", "F", "G", "C"],
13850
+ ["C", "F", "C", "G"],
13851
+ ["C", "Em", "F", "G"],
13852
+ ["F", "C", "G", "C"]
13853
+ ],
13854
+ b: [
13855
+ ["F", "G", "Em", "C"],
13856
+ ["F", "G", "C", "G"],
13857
+ ["G", "F", "C", "C"],
13858
+ ["FM7", "G", "Em", "F"],
13859
+ ["C", "G", "F", "C"],
13860
+ ["F", "Em", "F", "G"]
13861
+ ],
13862
+ c: [
13863
+ ["Em", "F", "G", "C"],
13864
+ ["F", "C", "G", "Em"],
13865
+ ["C", "Em", "F", "G"]
13866
+ ]
13867
+ };
13868
+ var MIYAKOBUSHI_CENTER = {
13869
+ tonic: "Em",
13870
+ half: "Am",
13871
+ deceptive: "F",
13872
+ tonicPattern: /^E(?:m|sus)/,
13873
+ a: [
13874
+ ["Em", "F", "Em", "Em"],
13875
+ ["Esus4", "F", "Esus4", "Em"],
13876
+ ["Em", "Am", "F", "Em"],
13877
+ ["Am", "Em", "F", "Em"],
13878
+ ["Em", "F", "Am", "Em"],
13879
+ ["Em", "Em", "F", "F"],
13880
+ ["Am", "F", "Em", "Em"],
13881
+ ["Em", "FM7", "Am", "Em"]
13882
+ ],
13883
+ b: [
13884
+ ["F", "Am", "Em", "Em"],
13885
+ ["Am", "F", "Em", "Em"],
13886
+ ["F", "Em", "Am", "Em"],
13887
+ ["FM7", "Am", "F", "Em"],
13888
+ ["Am", "Em", "F", "Am"],
13889
+ ["F", "Am", "F", "Em"]
13890
+ ],
13891
+ c: [
13892
+ ["Am", "Em", "F", "Am"],
13893
+ ["F", "Am", "Esus4", "Em"],
13894
+ ["Am", "F", "Em", "Em"]
13895
+ ]
13896
+ };
13897
+ var RITSU_CENTER = {
13898
+ tonic: "Dsus4",
13899
+ half: "G",
13900
+ deceptive: "Em",
13901
+ tonicPattern: /^Dsus/,
13902
+ a: [
13903
+ ["Dsus4", "G", "Dsus4", "Dsus4"],
13904
+ ["Dsus4", "Em", "G", "Dsus4"],
13905
+ ["G", "Dsus4", "Em", "Dsus4"],
13906
+ ["Dsus4", "G", "Em", "G"],
13907
+ ["Em", "G", "Dsus4", "Dsus4"],
13908
+ ["Dsus4", "Em7", "G", "Dsus4"],
13909
+ ["G", "Em", "Dsus4", "G"],
13910
+ ["Dsus4", "Am", "G", "Dsus4"]
13911
+ ],
13912
+ b: [
13913
+ ["G", "Em", "Dsus4", "Dsus4"],
13914
+ ["Em", "G", "Em", "Dsus4"],
13915
+ ["G", "Am", "Em", "Dsus4"],
13916
+ ["Em7", "G", "Dsus4", "Dsus4"],
13917
+ ["Am", "G", "Em", "Dsus4"],
13918
+ ["Dsus4", "Em", "G", "Em"]
13919
+ ],
13920
+ c: [
13921
+ ["Em", "Dsus4", "G", "Em"],
13922
+ ["G", "Em7", "Am", "G"],
13923
+ ["Am", "G", "Em", "Dsus4"]
13924
+ ]
13925
+ };
13926
+ var HARMONIC_MINOR_CENTER = {
13927
+ tonic: "Am",
13928
+ half: "E7",
13929
+ deceptive: "F",
13930
+ tonicPattern: /^Am/,
13931
+ a: [
13932
+ ["Am", "Dm", "E7", "Am"],
13933
+ // i-iv-V7-i。和声的短音階の基本形
13934
+ ["Am", "F", "E7", "Am"],
13935
+ ["Am", "E7", "Am", "Am"],
13936
+ ["Dm", "E7", "Am", "Am"],
13937
+ ["Am", "AmM7", "Dm", "E7"],
13938
+ // 主和音に導音を重ねたクリシェ
13939
+ ["Am", "F", "Dm", "E7"],
13940
+ ["F", "E7", "Am", "Am"],
13941
+ ["Am", "Bm7-5", "E7", "Am"]
13942
+ ],
13943
+ b: [
13944
+ ["Dm", "E7", "Am", "Am"],
13945
+ ["F", "E7", "Am", "E7"],
13946
+ ["Dm7", "G#dim", "Am", "E7"],
13947
+ ["F", "Dm", "E7", "Am"],
13948
+ ["Am", "Dm", "Bm7-5", "E7"],
13949
+ ["FM7", "E7", "Am", "Am"]
13950
+ ],
13951
+ c: [
13952
+ ["Dm", "Am", "Bm7-5", "E7"],
13953
+ ["F", "C+", "Dm", "E7"],
13954
+ ["Am", "F", "Dm", "E7"]
13955
+ ]
13956
+ };
13957
+ var HIJAZ_CENTER = {
13958
+ tonic: "E",
13959
+ half: "F",
13960
+ deceptive: "Am",
13961
+ tonicPattern: /^E(?![#b]|m)/,
13962
+ a: [
13963
+ ["E", "F", "E", "E"],
13964
+ // I-♭II。ヒジャーズの顔
13965
+ ["E7", "F", "E", "E"],
13966
+ ["Am", "F", "E", "E"],
13967
+ ["E", "F", "Dm", "E"],
13968
+ ["F", "E", "F", "E"],
13969
+ ["Dm", "E", "F", "E"],
13970
+ ["E7", "Am", "F", "E"],
13971
+ ["E", "Dm", "F", "E"]
13972
+ ],
13973
+ b: [
13974
+ ["F", "E", "Am", "E"],
13975
+ ["Dm", "C+", "F", "E"],
13976
+ ["Am", "Dm", "F", "E"],
13977
+ ["F", "Dm", "E", "E"],
13978
+ ["E7", "F", "Dm", "E"],
13979
+ ["Bm7-5", "E7", "Am", "E"]
13980
+ ],
13981
+ c: [
13982
+ ["Am", "Dm", "F", "E"],
13983
+ ["Dm", "Am", "F", "E7"],
13984
+ ["F", "C+", "Dm", "E"]
13985
+ ]
13986
+ };
13987
+ var HUNGARIAN_CENTER = {
13988
+ tonic: "Am",
13989
+ half: "E",
13990
+ deceptive: "F",
13991
+ tonicPattern: /^Am/,
13992
+ a: [
13993
+ ["Am", "E", "Am", "Am"],
13994
+ ["Am", "F", "E", "Am"],
13995
+ ["Am", "AmM7", "F", "E"],
13996
+ ["F", "E", "Am", "Am"],
13997
+ ["Am", "Fm", "E", "Am"],
13998
+ // ♭VIm。増2度を和音の側でも鳴らす
13999
+ ["Am", "E", "F", "E"],
14000
+ ["FM7", "E", "Am", "Am"],
14001
+ ["Am", "C+", "F", "E"]
14002
+ ],
14003
+ b: [
14004
+ ["F", "E", "Am", "E"],
14005
+ ["Fm", "E", "Am", "Am"],
14006
+ ["Am", "F", "C+", "E"],
14007
+ ["FM7", "Am", "F", "E"],
14008
+ ["E", "F", "E", "Am"],
14009
+ ["Am", "AmM7", "Fm", "E"]
14010
+ ],
14011
+ c: [
14012
+ ["F", "Am", "Fm", "E"],
14013
+ ["C+", "F", "Am", "E"],
14014
+ ["Am", "Fm", "F", "E"]
14015
+ ]
14016
+ };
14017
+ var BLUES_CENTER = {
14018
+ tonic: "C7",
14019
+ half: "G7",
14020
+ deceptive: "F7",
14021
+ tonicPattern: /^C7/,
14022
+ a: [
14023
+ ["C7", "C7", "C7", "C7"],
14024
+ // 12小節ブルースの1〜4小節
14025
+ ["C7", "F7", "C7", "C7"],
14026
+ ["C7", "C7", "F7", "F7"],
14027
+ ["F7", "F7", "C7", "C7"],
14028
+ // 5〜8小節
14029
+ ["C7", "F7", "C7", "G7"],
14030
+ ["C7", "C7", "G7", "F7"],
14031
+ ["C7", "F7", "G7", "C7"],
14032
+ ["F7", "C7", "G7", "C7"]
14033
+ ],
14034
+ b: [
14035
+ ["F7", "F7", "C7", "C7"],
14036
+ ["G7", "F7", "C7", "C7"],
14037
+ // 9〜12小節(ターンアラウンド)
14038
+ ["F7", "G7", "C7", "C7"],
14039
+ ["C7", "F7", "G7", "C7"],
14040
+ ["F7", "C7", "G7", "F7"],
14041
+ ["G7", "G7", "F7", "C7"]
14042
+ ],
14043
+ c: [
14044
+ ["G7", "F7", "C7", "G7"],
14045
+ ["F7", "F7", "G7", "G7"],
14046
+ ["C7", "C7", "F7", "G7"]
14047
+ ]
14048
+ };
14049
+ var COMPOSE_SCALES = {
14050
+ yo: {
14051
+ id: "yo",
14052
+ label: "\u967D\u97F3\u968E\uFF08\u9577\u8ABF\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF09",
14053
+ tonic: 0,
14054
+ core: [0, 1, 2, 4, 5],
14055
+ // ド レ ミ ソ ラ
14056
+ minorish: false,
14057
+ strict: false,
14058
+ 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"
14059
+ },
14060
+ minyo: {
14061
+ id: "minyo",
14062
+ label: "\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF09",
14063
+ tonic: 5,
14064
+ core: [5, 0, 1, 2, 4],
14065
+ // ラ ド レ ミ ソ
14066
+ minorish: true,
14067
+ strict: false,
14068
+ 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"
14069
+ },
14070
+ ritsu: {
14071
+ id: "ritsu",
14072
+ label: "\u5F8B\u97F3\u968E",
14073
+ tonic: 1,
14074
+ core: [1, 2, 4, 5, 6],
14075
+ // レ ミ ソ ラ シ
14076
+ minorish: true,
14077
+ strict: true,
14078
+ center: RITSU_CENTER,
14079
+ 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"
14080
+ },
14081
+ miyakobushi: {
14082
+ id: "miyakobushi",
14083
+ label: "\u90FD\u7BC0\u97F3\u968E\uFF08\u9670\u97F3\u968E\uFF09",
14084
+ tonic: 2,
14085
+ core: [2, 3, 5, 6, 0],
14086
+ // ミ ファ ラ シ ド
14087
+ minorish: true,
14088
+ strict: true,
14089
+ center: MIYAKOBUSHI_CENTER,
14090
+ 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"
14091
+ },
14092
+ ryukyu: {
14093
+ id: "ryukyu",
14094
+ label: "\u7409\u7403\u97F3\u968E",
14095
+ tonic: 0,
14096
+ core: [0, 2, 3, 4, 6],
14097
+ // ド ミ ファ ソ シ
14098
+ minorish: false,
14099
+ strict: true,
14100
+ center: RYUKYU_CENTER,
14101
+ 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"
14102
+ },
14103
+ dorian: {
14104
+ id: "dorian",
14105
+ label: "\u30C9\u30EA\u30A2\u30F3",
14106
+ tonic: 1,
14107
+ core: [1, 3, 4, 5, 0],
14108
+ // レ ファ ソ ラ ド
14109
+ minorish: true,
14110
+ strict: false,
14111
+ 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"
14112
+ },
14113
+ phrygian: {
14114
+ id: "phrygian",
14115
+ label: "\u30D5\u30EA\u30B8\u30A2\u30F3",
14116
+ tonic: 2,
14117
+ core: [2, 3, 5, 6, 1],
14118
+ // ミ ファ ラ シ レ
14119
+ minorish: true,
14120
+ strict: false,
14121
+ 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"
14122
+ },
14123
+ lydian: {
14124
+ id: "lydian",
14125
+ label: "\u30EA\u30C7\u30A3\u30A2\u30F3",
14126
+ tonic: 3,
14127
+ core: [3, 4, 6, 0, 2],
14128
+ // ファ ソ シ ド ミ
14129
+ minorish: false,
14130
+ strict: false,
14131
+ 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"
14132
+ },
14133
+ mixolydian: {
14134
+ id: "mixolydian",
14135
+ label: "\u30DF\u30AF\u30BD\u30EA\u30C7\u30A3\u30A2\u30F3",
14136
+ tonic: 4,
14137
+ core: [4, 5, 0, 1, 3],
14138
+ // ソ ラ ド レ ファ
14139
+ minorish: false,
14140
+ strict: false,
14141
+ 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"
14142
+ },
14143
+ harmonic_minor: {
14144
+ id: "harmonic_minor",
14145
+ label: "\u548C\u58F0\u7684\u77ED\u97F3\u968E",
14146
+ tonic: 5,
14147
+ parent: HARMONIC_MINOR_SCALE,
14148
+ core: [5, 0, 1, 2, 4],
14149
+ // ラ ド レ ミ ソ♯
14150
+ minorish: true,
14151
+ strict: false,
14152
+ center: HARMONIC_MINOR_CENTER,
14153
+ 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"
14154
+ },
14155
+ hijaz: {
14156
+ id: "hijaz",
14157
+ label: "\u30D2\u30B8\u30E3\u30FC\u30BA\uFF08\u30D5\u30EA\u30B8\u30A2\u30F3\u30FB\u30C9\u30DF\u30CA\u30F3\u30C8\uFF09",
14158
+ tonic: 2,
14159
+ parent: HARMONIC_MINOR_SCALE,
14160
+ core: [2, 3, 4, 5, 6],
14161
+ // ミ ファ ソ♯ ラ シ
14162
+ minorish: false,
14163
+ strict: false,
14164
+ center: HIJAZ_CENTER,
14165
+ 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"
14166
+ },
14167
+ hungarian: {
14168
+ id: "hungarian",
14169
+ label: "\u30CF\u30F3\u30AC\u30EA\u30A2\u30F3\u30FB\u30DE\u30A4\u30CA\u30FC\uFF08\u30B8\u30D7\u30B7\u30FC\uFF09",
14170
+ tonic: 5,
14171
+ parent: HUNGARIAN_SCALE,
14172
+ core: [5, 0, 1, 2, 4],
14173
+ // ラ ド レ♯ ミ ソ♯
14174
+ minorish: true,
14175
+ strict: false,
14176
+ center: HUNGARIAN_CENTER,
14177
+ 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"
14178
+ },
14179
+ blues: {
14180
+ id: "blues",
14181
+ label: "\u30D6\u30EB\u30FC\u30B9\u97F3\u968E",
14182
+ tonic: 0,
14183
+ parent: BLUES_SCALE,
14184
+ core: [0, 1, 2, 4, 5],
14185
+ // ド ミ♭ ファ ソ シ♭
14186
+ minorish: true,
14187
+ strict: true,
14188
+ center: BLUES_CENTER,
14189
+ 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"
14190
+ }
14191
+ };
14192
+ var COMPOSE_SCALE_IDS = Object.keys(
14193
+ COMPOSE_SCALES
14194
+ );
14195
+ var TONIC_CENTERS = {
14196
+ // --- レ(律・ドリアン) ---
14197
+ 1: {
14198
+ tonic: "Dm",
14199
+ half: "G",
14200
+ deceptive: "F",
14201
+ tonicPattern: /^Dm/,
14202
+ a: [
14203
+ ["Dm", "G", "Dm", "Dm"],
14204
+ // i-IV の往復。ドリアンの顔
14205
+ ["Dm", "Am", "G", "Dm"],
14206
+ ["Dm", "C", "G", "Dm"],
14207
+ ["Dm7", "G", "Dm7", "C"],
14208
+ ["Dm", "F", "C", "G"],
14209
+ ["Dm", "Em", "F", "G"],
14210
+ ["Dm", "G", "F", "C"],
14211
+ ["Dm7", "Em7", "FM7", "G"]
14212
+ ],
14213
+ b: [
14214
+ ["F", "G", "Am", "Dm"],
14215
+ ["C", "G", "Dm", "Dm"],
14216
+ ["G", "F", "C", "Dm"],
14217
+ ["Am", "G", "F", "Dm"],
14218
+ ["FM7", "G", "Em7", "Dm7"],
14219
+ ["Dm", "G", "C", "Am"]
14220
+ ],
14221
+ c: [
14222
+ ["Am", "Dm", "G", "C"],
14223
+ ["F", "Em", "Dm", "G"],
14224
+ ["Dm", "Am", "Em", "G"]
14225
+ ]
14226
+ },
14227
+ // --- ミ(都節・フリジアン) ---
14228
+ 2: {
14229
+ tonic: "Em",
14230
+ half: "Am",
14231
+ deceptive: "C",
14232
+ tonicPattern: /^Em/,
14233
+ a: [
14234
+ ["Em", "F", "Em", "Em"],
14235
+ // i-♭II。フリジアン/都節の顔
14236
+ ["Em", "Am", "F", "Em"],
14237
+ ["Am", "Em", "F", "Em"],
14238
+ ["Em", "F", "G", "Em"],
14239
+ ["Em", "Em", "F", "F"],
14240
+ ["Em", "C", "F", "Em"],
14241
+ ["Am", "F", "Em", "Em"],
14242
+ ["Em7", "FM7", "Em7", "Am7"]
14243
+ ],
14244
+ b: [
14245
+ ["F", "G", "Am", "Em"],
14246
+ ["Am", "G", "F", "Em"],
14247
+ ["F", "Em", "Am", "Em"],
14248
+ ["C", "F", "Em", "Em"],
14249
+ ["FM7", "G", "Em7", "Am"],
14250
+ ["Am", "Em", "F", "G"]
14251
+ ],
14252
+ c: [
14253
+ ["Am", "Em", "F", "C"],
14254
+ ["C", "G", "Am", "Em"],
14255
+ ["F", "C", "Am", "Em"]
14256
+ ]
14257
+ },
14258
+ // --- ファ(リディアン) ---
14259
+ 3: {
14260
+ tonic: "FM7",
14261
+ half: "C",
14262
+ deceptive: "Dm",
14263
+ tonicPattern: /^F(?![#b]|m)/,
14264
+ a: [
14265
+ ["FM7", "G", "FM7", "FM7"],
14266
+ // I-II。リディアンの顔(♯4 が G の3度に居る)
14267
+ ["FM7", "G", "Em", "Am"],
14268
+ ["F", "G", "C", "F"],
14269
+ ["FM7", "G", "Am", "F"],
14270
+ ["F", "C", "G", "F"],
14271
+ ["FM7", "Em7", "Dm7", "G"],
14272
+ ["F", "G", "F", "C"],
14273
+ ["FM7", "G", "Dm7", "F"]
14274
+ ],
14275
+ b: [
14276
+ ["G", "F", "C", "F"],
14277
+ ["Am", "G", "FM7", "FM7"],
14278
+ ["C", "G", "Am", "F"],
14279
+ ["G", "Em", "Am", "F"],
14280
+ ["Dm7", "G", "FM7", "FM7"],
14281
+ ["FM7", "G", "Em7", "F"]
14282
+ ],
14283
+ c: [
14284
+ ["Dm", "Am", "F", "G"],
14285
+ ["Am", "Em", "F", "G"],
14286
+ ["C", "Am", "Dm", "F"]
14287
+ ]
14288
+ },
14289
+ // --- ソ(ミクソリディアン) ---
14290
+ 4: {
14291
+ tonic: "G",
14292
+ half: "Dm",
14293
+ deceptive: "Em",
14294
+ tonicPattern: /^G(?![#b]|m)/,
14295
+ a: [
14296
+ ["G", "F", "C", "G"],
14297
+ // I-♭VII-IV。ミクソリディアンの顔
14298
+ ["G", "C", "F", "G"],
14299
+ ["G", "F", "G", "G"],
14300
+ ["C", "G", "F", "G"],
14301
+ ["G", "Dm", "F", "G"],
14302
+ ["G", "Am", "F", "G"],
14303
+ ["G", "F", "Dm", "C"],
14304
+ ["G", "C", "G", "F"]
14305
+ ],
14306
+ b: [
14307
+ ["F", "C", "G", "G"],
14308
+ ["Am", "F", "C", "G"],
14309
+ ["C", "Dm", "F", "G"],
14310
+ ["Em", "F", "C", "G"],
14311
+ ["F", "G", "Am", "G"],
14312
+ ["Dm7", "F", "C", "G"]
14313
+ ],
14314
+ c: [
14315
+ ["Am", "Em", "F", "G"],
14316
+ ["C", "Am", "Dm", "G"],
14317
+ ["Em", "Am", "F", "C"]
14318
+ ]
14319
+ }
14320
+ };
14321
+ var scaleDegrees = (scale) => scale.parent ?? MAJOR_SCALE2;
14322
+ var scaleSize = (scale) => scaleDegrees(scale).length;
14323
+ var degreeToPitch = (scale, degree) => {
14324
+ const list = scaleDegrees(scale);
14325
+ const size = list.length;
14326
+ const index = (degree % size + size) % size;
14327
+ const octave = Math.floor(degree / size);
14328
+ const d = list[index];
14329
+ return { semi: d.semi + octave * 12, fifth: d.fifth };
14330
+ };
14331
+ var semitoneToDegree = (scale, semi) => {
14332
+ const list = scaleDegrees(scale);
14333
+ const octave = Math.floor(semi / 12);
14334
+ const within = semi - octave * 12;
14335
+ let best = 0;
14336
+ let bestDist = Number.POSITIVE_INFINITY;
14337
+ for (let i2 = 0; i2 < list.length; i2++) {
14338
+ const dist = Math.abs(list[i2].semi - within);
14339
+ if (dist < bestDist) {
14340
+ bestDist = dist;
14341
+ best = i2;
14342
+ }
14343
+ }
14344
+ return octave * list.length + best;
14345
+ };
14346
+ var walk = (scale, semi, delta) => degreeToPitch(scale, semitoneToDegree(scale, semi) + delta).semi;
14347
+ var scaleFifth = (scale, semi) => degreeToPitch(scale, semitoneToDegree(scale, semi)).fifth;
14348
+ var SCALE_PCS = /* @__PURE__ */ new Map();
14349
+ var scalePcs = (scale) => {
14350
+ const hit = SCALE_PCS.get(scale.id);
14351
+ if (hit) return hit;
14352
+ const set = new Set(
14353
+ scaleDegrees(scale).map((d) => (d.semi % 12 + 12) % 12)
14354
+ );
14355
+ SCALE_PCS.set(scale.id, set);
14356
+ return set;
14357
+ };
14358
+ var CORE_SORTED = /* @__PURE__ */ new Map();
14359
+ var sortedCore = (scale) => {
14360
+ const hit = CORE_SORTED.get(scale.id);
14361
+ if (hit) return hit;
14362
+ const sorted = [...scale.core].sort((a, b) => a - b);
14363
+ CORE_SORTED.set(scale.id, sorted);
14364
+ return sorted;
14365
+ };
14366
+ var CORE_PCS = /* @__PURE__ */ new Map();
14367
+ var corePcs = (scale) => {
14368
+ const hit = CORE_PCS.get(scale.id);
14369
+ if (hit) return hit;
14370
+ const list = scaleDegrees(scale);
14371
+ const set = new Set(scale.core.map((d) => (list[d].semi % 12 + 12) % 12));
14372
+ CORE_PCS.set(scale.id, set);
14373
+ return set;
14374
+ };
14375
+ var isCoreDegree = (scale, degree) => {
14376
+ const size = scaleSize(scale);
14377
+ return sortedCore(scale).includes((degree % size + size) % size);
14378
+ };
14379
+ var coreToDegree = (scale, step) => {
14380
+ const sorted = sortedCore(scale);
14381
+ const n = sorted.length;
14382
+ const index = (step % n + n) % n;
14383
+ const octave = Math.floor(step / n);
14384
+ return sorted[index] + octave * scaleSize(scale);
14385
+ };
14386
+ var degreeToCore = (scale, degree) => {
14387
+ const sorted = sortedCore(scale);
14388
+ const size = scaleSize(scale);
14389
+ const index = (degree % size + size) % size;
14390
+ const octave = Math.floor(degree / size);
14391
+ let best = 0;
14392
+ let bestDist = Number.POSITIVE_INFINITY;
14393
+ for (let i2 = 0; i2 < sorted.length; i2++) {
14394
+ const dist = Math.abs(sorted[i2] - index);
14395
+ if (dist < bestDist) {
14396
+ bestDist = dist;
14397
+ best = i2;
14398
+ }
14399
+ }
14400
+ return octave * sorted.length + best;
14401
+ };
14402
+ var isOutsideCore = (scale, semi) => scale.strict ? !corePcs(scale).has((semi % 12 + 12) % 12) : !isCoreDegree(scale, semitoneToDegree(scale, semi));
14403
+ var resolveComposeScale = (choice, minorKey, rnd = Math.random) => {
14404
+ const c = (choice ?? "").trim() || "auto";
14405
+ const hit = COMPOSE_SCALES[c];
14406
+ if (hit) return hit;
14407
+ if (c === "any")
14408
+ return COMPOSE_SCALES[COMPOSE_SCALE_IDS[Math.floor(rnd() * COMPOSE_SCALE_IDS.length)] ?? "yo"];
14409
+ return COMPOSE_SCALES[minorKey ? "minyo" : "yo"];
14410
+ };
14411
+ var getComposeScaleDescription = (choice) => {
14412
+ const hit = COMPOSE_SCALES[choice];
14413
+ if (hit) return hit.description;
14414
+ if (choice === "any")
14415
+ return `${COMPOSE_SCALE_IDS.length}\u3064\u306E\u97F3\u968E\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059`;
14416
+ 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";
14417
+ };
14418
+ var resolveCenter = (scale) => scale.center ?? TONIC_CENTERS[scale.tonic] ?? null;
14419
+
13656
14420
  // src/compose-sections.ts
13657
14421
  var SECTION_LABELS = {
13658
14422
  intro: "\u30A4\u30F3\u30C8\u30ED",
@@ -13888,6 +14652,7 @@ var sectionAt = (plan, bar) => {
13888
14652
  // src/compose.ts
13889
14653
  var STEP_SEMITONES = 2;
13890
14654
  var MAX_LEAP_SEMITONES = 10;
14655
+ var LEAP_CEILINGS = [7, 8, 9, 10, 10, 12, 14, 16];
13891
14656
  var MAX_BAR_LEAP_SEMITONES = 10;
13892
14657
  var HARD = {
13893
14658
  /** メロディが1音も無い、音域が半音未満(同じ音を並べただけ)。 */
@@ -13946,13 +14711,16 @@ var WEIGHTS = {
13946
14711
  // --- 直近に作った曲と違うか ---
13947
14712
  novelty: 1.2
13948
14713
  };
14714
+ var DEVIATION_BUDGET = CORPUS_DEVIATION_BUDGET;
14715
+ var BUDGETED_KEYS = new Set(CORPUS_PROFILE_KEYS);
13949
14716
  var HAND_BANDS = {
13950
14717
  /** サブメロの音数/小節。少なすぎると「置いただけ」、多すぎるとメロディを食う。 */
13951
14718
  subDensity: [0.5, 1.8, 4.5, 8],
13952
14719
  complementarity: [0.05, 0.25, 0.7, 0.95],
13953
14720
  climaxPeaks: [0, 1, 2, 5]
13954
14721
  };
13955
- var DRAW_COUNT = 40;
14722
+ var DRAW_COUNT = 12;
14723
+ var SELECT_TEMPERATURE = 0.05;
13956
14724
  var BASE_STEPS_PER_BAR = 192;
13957
14725
  var WHOLE = 192;
13958
14726
  var DOT_HALF = 144;
@@ -14077,65 +14845,6 @@ var BPM_CHOICES = [
14077
14845
  180,
14078
14846
  185
14079
14847
  ];
14080
- var MAJOR_SCALE2 = [
14081
- { semi: 0, fifth: 0 },
14082
- // C
14083
- { semi: 2, fifth: 2 },
14084
- // D
14085
- { semi: 4, fifth: 4 },
14086
- // E
14087
- { semi: 5, fifth: -1 },
14088
- // F
14089
- { semi: 7, fifth: 1 },
14090
- // G
14091
- { semi: 9, fifth: 3 },
14092
- // A
14093
- { semi: 11, fifth: 5 }
14094
- // B
14095
- ];
14096
- var degreeToPitch = (degree) => {
14097
- const index = (degree % 7 + 7) % 7;
14098
- const octave = Math.floor(degree / 7);
14099
- const d = MAJOR_SCALE2[index];
14100
- return { semi: d.semi + octave * 12, fifth: d.fifth };
14101
- };
14102
- var semitoneToDegree = (semi) => {
14103
- const octave = Math.floor(semi / 12);
14104
- const within = semi - octave * 12;
14105
- let best = 0;
14106
- let bestDist = Number.POSITIVE_INFINITY;
14107
- for (let i2 = 0; i2 < MAJOR_SCALE2.length; i2++) {
14108
- const dist = Math.abs(MAJOR_SCALE2[i2].semi - within);
14109
- if (dist < bestDist) {
14110
- bestDist = dist;
14111
- best = i2;
14112
- }
14113
- }
14114
- return octave * 7 + best;
14115
- };
14116
- var walk = (semi, delta) => degreeToPitch(semitoneToDegree(semi) + delta).semi;
14117
- var NON_PENTATONIC_DEGREES = /* @__PURE__ */ new Set([3, 6]);
14118
- var isNonPentatonic = (semi) => NON_PENTATONIC_DEGREES.has((semitoneToDegree(semi) % 7 + 7) % 7);
14119
- var PENTATONIC_DEGREES = [0, 1, 2, 4, 5];
14120
- var pentaToDegree = (penta) => {
14121
- const index = (penta % 5 + 5) % 5;
14122
- const octave = Math.floor(penta / 5);
14123
- return PENTATONIC_DEGREES[index] + octave * 7;
14124
- };
14125
- var degreeToPenta = (degree) => {
14126
- const index = (degree % 7 + 7) % 7;
14127
- const octave = Math.floor(degree / 7);
14128
- let best = 0;
14129
- let bestDist = Number.POSITIVE_INFINITY;
14130
- for (let i2 = 0; i2 < PENTATONIC_DEGREES.length; i2++) {
14131
- const dist = Math.abs(PENTATONIC_DEGREES[i2] - index);
14132
- if (dist < bestDist) {
14133
- bestDist = dist;
14134
- best = i2;
14135
- }
14136
- }
14137
- return octave * 5 + best;
14138
- };
14139
14848
  var SECTION_A_PROGRESSIONS = [
14140
14849
  ["C", "G", "Am", "Em7"],
14141
14850
  // カノン進行の前半
@@ -14254,6 +14963,22 @@ var SECTION_DECEPTIVE_DERIVATIONS = [
14254
14963
  (a, t) => [a[0], "Dm7", "G7", t === "Am" ? "FM7" : "Am7"],
14255
14964
  (a, t) => [a[0], "F", "G7", t === "Am" ? "F" : "Am7"]
14256
14965
  ];
14966
+ var MODAL_FULL_DERIVATIONS = [
14967
+ (a, c) => [a[0], a[1], c.half, c.tonic],
14968
+ (a, c) => [a[0], c.half, a[2], c.tonic],
14969
+ (a, c) => [a[0], a[1], a[2], c.tonic],
14970
+ (a, c) => [c.tonic, c.half, a[2], c.tonic]
14971
+ ];
14972
+ var MODAL_HALF_DERIVATIONS = [
14973
+ (a, c) => [a[0], a[1], a[2], c.half],
14974
+ (a, c) => [a[0], a[1], c.tonic, c.half],
14975
+ (a, c) => [a[0], c.tonic, a[2], c.half]
14976
+ ];
14977
+ var MODAL_DECEPTIVE_DERIVATIONS = [
14978
+ (a, c) => [a[0], a[1], c.half, c.deceptive],
14979
+ (a, c) => [a[0], c.half, a[2], c.deceptive],
14980
+ (a, c) => [a[0], a[1], a[2], c.deceptive]
14981
+ ];
14257
14982
  var MODAL_BORROW = {
14258
14983
  F: "Fm",
14259
14984
  // IV → IVm(サブドミナントマイナー。いちばん定番)
@@ -14711,6 +15436,14 @@ for (const c of [...MOTIF_CELLS, ...RHYTHM_CELLS]) {
14711
15436
  const key = onsetKeyOf(c.value);
14712
15437
  cellEntryCount.set(key, (cellEntryCount.get(key) ?? 0) + 1);
14713
15438
  }
15439
+ var resolveMelodyForm = (choice, rnd) => {
15440
+ const c = (choice ?? "").trim() || "auto";
15441
+ if (c === "motif" || c === "ostinato" || c === "through") return c;
15442
+ const r = rnd();
15443
+ if (r < 0.25) return "ostinato";
15444
+ if (r < 0.4) return "through";
15445
+ return "motif";
15446
+ };
14714
15447
  var groovyCells = (cells, groove, rnd) => {
14715
15448
  const hasSixteenth = (c) => c.value.some((v) => Math.abs(v) <= SIXTEENTH);
14716
15449
  if (groove === "eighth") {
@@ -14844,7 +15577,7 @@ var nearestChordTone = (targetSemi, tones, minWeight, preferColor = false) => {
14844
15577
  var MELODY_LOW = 60;
14845
15578
  var MELODY_HIGH = 81;
14846
15579
  var MELODY_CENTER = (MELODY_LOW + MELODY_HIGH) / 2;
14847
- var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) => {
15580
+ var harmonyPitch = (scale, melodySemi, tones, prevHarmony, offset, parallel, against) => {
14848
15581
  const lo = Math.max(-12, Math.min(offset, 0) - 7);
14849
15582
  const hi = Math.min(12, Math.max(offset, 0) + 7);
14850
15583
  let best = null;
@@ -14862,7 +15595,8 @@ var harmonyPitch = (melodySemi, tones, prevHarmony, offset, parallel, against) =
14862
15595
  const g = Math.abs(semi - against) % 12;
14863
15596
  clash = g === 3 || g === 4 || g === 8 || g === 9 || g === 7 ? 0 : g === 0 ? 0.5 : 1.2;
14864
15597
  }
14865
- const cost = stay + clash + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
15598
+ const offCore = scale.strict && isOutsideCore(scale, semi) ? 1.5 : 0;
15599
+ const cost = stay + clash + offCore + Math.abs(delta - offset) * (parallel ? 1.4 : 0.7) + (3 - tone.weight) * 0.4;
14866
15600
  if (cost < bestCost) {
14867
15601
  bestCost = cost;
14868
15602
  best = { semi, fifth: tone.fifth };
@@ -14885,7 +15619,7 @@ var clampSemi = (semi, low, high) => {
14885
15619
  while (s > high) s -= 12;
14886
15620
  return s;
14887
15621
  };
14888
- var leapTarget = (from, tones, rnd) => {
15622
+ var leapTarget = (from, tones, rnd, maxLeap = MAX_LEAP_SEMITONES) => {
14889
15623
  const candidates = [];
14890
15624
  for (const tone of tones) {
14891
15625
  const base = pitchClass(tone.semi);
@@ -14893,7 +15627,7 @@ var leapTarget = (from, tones, rnd) => {
14893
15627
  const semi = base + oct * 12;
14894
15628
  if (semi < MELODY_LOW || semi > MELODY_HIGH) continue;
14895
15629
  const gap = Math.abs(semi - from);
14896
- if (gap >= 3 && gap <= MAX_LEAP_SEMITONES) candidates.push(semi);
15630
+ if (gap >= 3 && gap <= maxLeap) candidates.push(semi);
14897
15631
  }
14898
15632
  }
14899
15633
  if (candidates.length === 0) return null;
@@ -14907,40 +15641,43 @@ var leapTarget = (from, tones, rnd) => {
14907
15641
  ];
14908
15642
  return pick(weighted.length > 0 ? weighted : candidates, rnd);
14909
15643
  };
14910
- var landOn = (degrees, scaleIndex) => {
15644
+ var landOn = (scale, degrees, scaleIndex) => {
14911
15645
  if (degrees.length === 0) return;
15646
+ const size = scaleSize(scale);
14912
15647
  const last = degrees[degrees.length - 1];
14913
- const index = (last % 7 + 7) % 7;
15648
+ const index = (last % size + size) % size;
14914
15649
  let delta = scaleIndex - index;
14915
- if (delta > 3) delta -= 7;
14916
- if (delta < -3) delta += 7;
15650
+ if (delta > size / 2) delta -= size;
15651
+ if (delta < -size / 2) delta += size;
14917
15652
  degrees[degrees.length - 1] = last + delta;
14918
15653
  };
14919
- var landPitch = (pitches, scaleIndex) => {
15654
+ var landPitch = (scale, pitches, scaleIndex) => {
14920
15655
  if (pitches.length === 0) return;
15656
+ const size = scaleSize(scale);
14921
15657
  const last = pitches[pitches.length - 1];
14922
- const degree = semitoneToDegree(last);
14923
- const index = (degree % 7 + 7) % 7;
15658
+ const degree = semitoneToDegree(scale, last);
15659
+ const index = (degree % size + size) % size;
14924
15660
  let delta = scaleIndex - index;
14925
- if (delta > 3) delta -= 7;
14926
- if (delta < -3) delta += 7;
15661
+ if (delta > size / 2) delta -= size;
15662
+ if (delta < -size / 2) delta += size;
14927
15663
  pitches[pitches.length - 1] = clampSemi(
14928
- degreeToPitch(degree + delta).semi,
15664
+ degreeToPitch(scale, degree + delta).semi,
14929
15665
  MELODY_LOW,
14930
15666
  MELODY_HIGH
14931
15667
  );
14932
15668
  };
14933
- var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourOffset, repeatShift, barHeadWeight, preferColor, quarterSteps, rnd) => {
15669
+ var barDegrees = (role, slots, tones, style, scale, motifContour, startDegree, contourOffset, repeatShift, barHeadWeight, preferColor, quarterSteps, rnd) => {
14934
15670
  const noteCount = slots.length;
14935
15671
  const out = [];
14936
15672
  if (role === "motif" || role === "sequence" || role === "climax" || role === "answer") {
14937
15673
  const shift = (role === "sequence" ? pick([-2, -1, 1, 2], rnd) : role === "climax" ? 5 : 0) + repeatShift;
14938
15674
  if (style.pentatonicMotif) {
14939
- const startPenta = degreeToPenta(startDegree);
15675
+ const startCore = degreeToCore(scale, startDegree);
14940
15676
  for (let i2 = 0; i2 < noteCount; i2++)
14941
15677
  out.push(
14942
- pentaToDegree(
14943
- startPenta + shift + motifContour[(contourOffset + i2) % motifContour.length]
15678
+ coreToDegree(
15679
+ scale,
15680
+ startCore + shift + motifContour[(contourOffset + i2) % motifContour.length]
14944
15681
  )
14945
15682
  );
14946
15683
  } else {
@@ -14960,9 +15697,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14960
15697
  for (let i2 = 0; i2 < noteCount; i2++)
14961
15698
  out.push(startDegree + dir2 * (i2 < peak ? i2 : peak * 2 - i2 - 1));
14962
15699
  } else if (style.runShape === "broken") {
14963
- const arp = tones.map((t) => semitoneToDegree(clampSemi(t.semi, 60, 71))).sort((a, b) => a - b);
15700
+ const arp = tones.map((t) => semitoneToDegree(scale, clampSemi(t.semi, 60, 71))).sort((a, b) => a - b);
14964
15701
  for (let i2 = 0; i2 < noteCount; i2++) {
14965
- const oct = Math.floor(i2 / arp.length) * 7;
15702
+ const oct = Math.floor(i2 / arp.length) * scaleSize(scale);
14966
15703
  const idx = dir2 > 0 ? i2 % arp.length : arp.length - 1 - i2 % arp.length;
14967
15704
  out.push(arp[idx] + dir2 * oct);
14968
15705
  }
@@ -14985,7 +15722,10 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14985
15722
  return out;
14986
15723
  }
14987
15724
  if (role === "cadence") {
14988
- const tonic = semitoneToDegree(clampSemi(72, MELODY_LOW, MELODY_HIGH));
15725
+ const tonic = semitoneToDegree(
15726
+ scale,
15727
+ clampSemi(72, MELODY_LOW, MELODY_HIGH)
15728
+ );
14989
15729
  for (let i2 = 0; i2 < noteCount; i2++) {
14990
15730
  if (style.cadenceShape === "descend") {
14991
15731
  out.push(tonic + noteCount - 1 - i2);
@@ -14993,7 +15733,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
14993
15733
  const shape = [4, 2, 0];
14994
15734
  out.push(tonic + shape[Math.min(i2, shape.length - 1)]);
14995
15735
  } else if (style.cadenceShape === "leap-up") {
14996
- out.push(i2 === noteCount - 1 ? tonic : tonic - 7 + Math.min(i2, 4));
15736
+ out.push(
15737
+ i2 === noteCount - 1 ? tonic : tonic - scaleSize(scale) + Math.min(i2, 4)
15738
+ );
14997
15739
  } else {
14998
15740
  out.push(i2 === 0 && noteCount > 1 ? tonic + 1 : tonic);
14999
15741
  }
@@ -15028,8 +15770,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
15028
15770
  for (let i2 = 0; i2 < out.length; i2++) {
15029
15771
  if (!slots[i2].isStrong) continue;
15030
15772
  out[i2] = semitoneToDegree(
15773
+ scale,
15031
15774
  nearestChordTone(
15032
- degreeToPitch(out[i2]).semi,
15775
+ degreeToPitch(scale, out[i2]).semi,
15033
15776
  tones,
15034
15777
  barHeadWeight,
15035
15778
  preferColor
@@ -15040,9 +15783,9 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
15040
15783
  if (slots[i2].isStrong) continue;
15041
15784
  if (slots[i2].value >= quarterSteps) {
15042
15785
  if (rnd() < style.leapAffinity) {
15043
- const from = degreeToPitch(out[i2 - 1]).semi;
15044
- const target = leapTarget(from, tones, rnd);
15045
- if (target !== null) out[i2] = semitoneToDegree(target);
15786
+ const from = degreeToPitch(scale, out[i2 - 1]).semi;
15787
+ const target = leapTarget(from, tones, rnd, style.maxLeap);
15788
+ if (target !== null) out[i2] = semitoneToDegree(scale, target);
15046
15789
  }
15047
15790
  continue;
15048
15791
  }
@@ -15052,20 +15795,20 @@ var barDegrees = (role, slots, tones, style, motifContour, startDegree, contourO
15052
15795
  }
15053
15796
  return out;
15054
15797
  };
15055
- var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, preferShift) => {
15798
+ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, scale, pentatonic, preferShift, maxShift = 3) => {
15056
15799
  let best = degrees;
15057
15800
  let bestShift = 0;
15058
15801
  let bestScore = Number.NEGATIVE_INFINITY;
15059
15802
  let preferScore = Number.NEGATIVE_INFINITY;
15060
15803
  let preferMoved = null;
15061
- for (let shift = -3; shift <= 3; shift++) {
15804
+ for (let shift = -maxShift; shift <= maxShift; shift++) {
15062
15805
  const moved = degrees.map(
15063
- (d) => pentatonic ? pentaToDegree(degreeToPenta(d) + shift) : d + shift
15806
+ (d) => pentatonic ? coreToDegree(scale, degreeToCore(scale, d) + shift) : d + shift
15064
15807
  );
15065
15808
  let score = 0;
15066
15809
  for (let i2 = 0; i2 < moved.length; i2++) {
15067
15810
  const semi = clampSemi(
15068
- degreeToPitch(moved[i2]).semi,
15811
+ degreeToPitch(scale, moved[i2]).semi,
15069
15812
  MELODY_LOW,
15070
15813
  MELODY_HIGH
15071
15814
  );
@@ -15074,7 +15817,7 @@ var fitMotif = (degrees, slots, tones, prevSemi, quarterSteps, pentatonic, prefe
15074
15817
  score += important ? w * 3 : w;
15075
15818
  }
15076
15819
  const head = clampSemi(
15077
- degreeToPitch(moved[0]).semi,
15820
+ degreeToPitch(scale, moved[0]).semi,
15078
15821
  MELODY_LOW,
15079
15822
  MELODY_HIGH
15080
15823
  );
@@ -15097,7 +15840,7 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
15097
15840
  const out = [];
15098
15841
  let prev = prevSemi;
15099
15842
  if (opts.preserveContour) {
15100
- const raw = degrees.map((d) => degreeToPitch(d).semi);
15843
+ const raw = degrees.map((d) => degreeToPitch(opts.scale, d).semi);
15101
15844
  const lo = Math.min(...raw);
15102
15845
  const hi = Math.max(...raw);
15103
15846
  let shift = 0;
@@ -15111,30 +15854,42 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
15111
15854
  }
15112
15855
  for (let i2 = 0; i2 < degrees.length; i2++) {
15113
15856
  let semi = clampSemi(
15114
- degreeToPitch(degrees[i2]).semi,
15857
+ degreeToPitch(opts.scale, degrees[i2]).semi,
15115
15858
  MELODY_LOW,
15116
15859
  MELODY_HIGH
15117
15860
  );
15118
- const limit = i2 === 0 ? MAX_BAR_LEAP_SEMITONES : MAX_LEAP_SEMITONES;
15861
+ const limit = i2 === 0 ? MAX_BAR_LEAP_SEMITONES : opts.maxLeap;
15119
15862
  if (!opts.allowLeap && Math.abs(semi - prev) > limit) {
15120
15863
  semi = clampSemi(
15121
- walk(prev, Math.sign(semi - prev) * 3),
15864
+ walk(opts.scale, prev, Math.sign(semi - prev) * 3),
15122
15865
  MELODY_LOW,
15123
15866
  MELODY_HIGH
15124
15867
  );
15125
15868
  }
15126
15869
  if (!opts.allowArpeggio && i2 >= 2 && Math.abs(out[i2 - 1] - out[i2 - 2]) > STEP_SEMITONES) {
15127
15870
  const back = -Math.sign(out[i2 - 1] - out[i2 - 2]);
15128
- semi = clampSemi(walk(out[i2 - 1], back), MELODY_LOW, MELODY_HIGH);
15871
+ semi = clampSemi(
15872
+ walk(opts.scale, out[i2 - 1], back),
15873
+ MELODY_LOW,
15874
+ MELODY_HIGH
15875
+ );
15129
15876
  }
15130
15877
  if (toneWeight(semi, tones) === 0 && (slots[i2].isStrong || slots[i2].value >= opts.quarterSteps)) {
15131
15878
  const resolved = semi + 1;
15132
- const useResolved = resolved <= MELODY_HIGH && !DIATONIC_PCS.has(pitchClass(resolved)) && toneWeight(resolved, tones) >= 2 && resolved !== prev && opts.rnd() < opts.chromaticAffinity;
15879
+ const useResolved = resolved <= MELODY_HIGH && !scalePcs(opts.scale).has(pitchClass(resolved)) && toneWeight(resolved, tones) >= 2 && resolved !== prev && opts.rnd() < opts.chromaticAffinity;
15133
15880
  if (useResolved) {
15134
15881
  semi = resolved;
15135
15882
  } else {
15136
- const up = clampSemi(walk(semi, 1), MELODY_LOW, MELODY_HIGH);
15137
- const down = clampSemi(walk(semi, -1), MELODY_LOW, MELODY_HIGH);
15883
+ const up = clampSemi(
15884
+ walk(opts.scale, semi, 1),
15885
+ MELODY_LOW,
15886
+ MELODY_HIGH
15887
+ );
15888
+ const down = clampSemi(
15889
+ walk(opts.scale, semi, -1),
15890
+ MELODY_LOW,
15891
+ MELODY_HIGH
15892
+ );
15138
15893
  const score = (s) => toneWeight(s, tones) * 2 + (i2 > 0 && s === prev ? -3 : 0);
15139
15894
  semi = score(down) >= score(up) ? down : up;
15140
15895
  }
@@ -15142,7 +15897,14 @@ var shapeBar = (degrees, slots, tones, prevSemi, opts) => {
15142
15897
  out.push(semi);
15143
15898
  prev = semi;
15144
15899
  }
15145
- applyPentatonic(out, slots, tones, prevSemi, opts.quarterSteps / 2);
15900
+ applyPentatonic(
15901
+ out,
15902
+ slots,
15903
+ tones,
15904
+ prevSemi,
15905
+ opts.quarterSteps / 2,
15906
+ opts.scale
15907
+ );
15146
15908
  applyOctaveJumps(out, slots, opts.octaveAffinity, opts.rnd);
15147
15909
  return out;
15148
15910
  };
@@ -15159,27 +15921,27 @@ var applyOctaveJumps = (out, slots, affinity, rnd) => {
15159
15921
  out[i2] = canUp && (!canDown || rnd() < 0.5) ? up : down;
15160
15922
  }
15161
15923
  };
15162
- var applyPentatonic = (out, slots, tones, prevSemi, shortSteps) => {
15924
+ var applyPentatonic = (out, slots, tones, prevSemi, shortSteps, scale) => {
15163
15925
  for (let i2 = 0; i2 < out.length; i2++) {
15164
15926
  const semi = out[i2];
15165
- if (!isNonPentatonic(semi)) continue;
15166
- if (tones.some((t) => pitchClass(t.semi) === pitchClass(semi))) continue;
15927
+ if (!isOutsideCore(scale, semi)) continue;
15928
+ if (!scale.strict && tones.some((t) => pitchClass(t.semi) === pitchClass(semi)))
15929
+ continue;
15167
15930
  const before = i2 === 0 ? prevSemi : out[i2 - 1];
15168
15931
  const after = i2 + 1 < out.length ? out[i2 + 1] : null;
15169
15932
  const inByStep = Math.abs(semi - before) <= STEP_SEMITONES;
15170
15933
  const outByStep = after !== null && Math.abs(after - semi) <= STEP_SEMITONES;
15171
- if (inByStep || outByStep) continue;
15172
- if (!slots[i2].isStrong && slots[i2].value <= shortSteps) continue;
15173
- const up = clampSemi(walk(semi, 1), MELODY_LOW, MELODY_HIGH);
15174
- const down = clampSemi(walk(semi, -1), MELODY_LOW, MELODY_HIGH);
15175
- const score = (s) => (isNonPentatonic(s) ? -4 : 0) + toneWeight(s, tones) + (s === before ? -3 : 0) + (after !== null ? -Math.abs(after - s) / 12 : 0);
15934
+ if (scale.strict ? inByStep && outByStep : inByStep || outByStep) continue;
15935
+ if (!scale.strict && !slots[i2].isStrong && slots[i2].value <= shortSteps)
15936
+ continue;
15937
+ const up = clampSemi(walk(scale, semi, 1), MELODY_LOW, MELODY_HIGH);
15938
+ const down = clampSemi(walk(scale, semi, -1), MELODY_LOW, MELODY_HIGH);
15939
+ const score = (s) => (isOutsideCore(scale, s) ? -4 : 0) + toneWeight(s, tones) + (s === before ? -3 : 0) + (after !== null ? -Math.abs(after - s) / 12 : 0);
15176
15940
  out[i2] = score(down) >= score(up) ? down : up;
15177
15941
  }
15178
15942
  };
15179
- var DIATONIC_PCS = new Set(MAJOR_SCALE2.map((d) => d.semi));
15180
15943
  var SHARP_FIFTHS = [0, 7, 2, 9, 4, -1, 6, 1, 8, 3, 10, 5];
15181
15944
  var FLAT_FIFTHS = [0, -5, 2, -3, 4, -1, -6, 1, -4, 3, -2, 5];
15182
- var diatonicFifth = (semi) => degreeToPitch(semitoneToDegree(semi)).fifth;
15183
15945
  var nearestOctaveOf = (near, semi) => {
15184
15946
  const pc = pitchClass(semi);
15185
15947
  let best = pc;
@@ -15194,7 +15956,7 @@ var nearestOctaveOf = (near, semi) => {
15194
15956
  }
15195
15957
  return best;
15196
15958
  };
15197
- var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15959
+ var applyChromatic = (scale, pitches, fifths, slots, tones, opts) => {
15198
15960
  const last = pitches.length - 1;
15199
15961
  for (let i2 = 0; i2 < pitches.length; i2++) {
15200
15962
  const tone = tones.find(
@@ -15202,7 +15964,8 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15202
15964
  );
15203
15965
  if (tone) fifths[i2] = tone.fifth;
15204
15966
  }
15205
- const altered = tones.filter((t) => !DIATONIC_PCS.has(pitchClass(t.semi)));
15967
+ const pcs = scalePcs(scale);
15968
+ const altered = tones.filter((t) => !pcs.has(pitchClass(t.semi)));
15206
15969
  for (let i2 = 0; i2 < pitches.length; i2++) {
15207
15970
  if (opts.keepLast && i2 === last) continue;
15208
15971
  if (!slots[i2].isStrong && slots[i2].value < opts.quarterSteps) continue;
@@ -15230,7 +15993,7 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15230
15993
  const span = Math.abs(after - before);
15231
15994
  const target = span === 2 ? before + dir : span === 3 ? after - dir : null;
15232
15995
  if (target === null) continue;
15233
- if (DIATONIC_PCS.has(pitchClass(target))) continue;
15996
+ if (pcs.has(pitchClass(target))) continue;
15234
15997
  if (target === before || target === after) continue;
15235
15998
  if (target < MELODY_LOW || target > MELODY_HIGH) continue;
15236
15999
  pitches[i2] = target;
@@ -15238,7 +16001,8 @@ var applyChromatic = (pitches, fifths, slots, tones, opts) => {
15238
16001
  lastAltered = i2;
15239
16002
  }
15240
16003
  };
15241
- var draw = (options, resolvedKey, rnd) => {
16004
+ var draw = (options, resolvedKey, scale, rnd) => {
16005
+ const center = resolveCenter(scale);
15242
16006
  const stepsPerBar = options.stepsPerBar;
15243
16007
  const edo = options.edo === 31 ? 31 : 12;
15244
16008
  const scaleStep = (v) => Math.max(1, Math.round(Math.abs(v) * stepsPerBar / BASE_STEPS_PER_BAR)) * Math.sign(v);
@@ -15266,7 +16030,7 @@ var draw = (options, resolvedKey, rnd) => {
15266
16030
  const totalBars = sectionPlan.reduce((sum, s) => sum + s.bars, 0);
15267
16031
  const relativeKinds = /* @__PURE__ */ new Set();
15268
16032
  let relativeShift = 0;
15269
- if (rnd() < 0.25) {
16033
+ if (rnd() < 0.25 && !center) {
15270
16034
  for (const kind of pick(
15271
16035
  [["prechorus"], ["bridge"], ["prechorus", "bridge"]],
15272
16036
  rnd
@@ -15376,42 +16140,42 @@ var draw = (options, resolvedKey, rnd) => {
15376
16140
  barKeyShift[b] = s.keyShift;
15377
16141
  }
15378
16142
  }
15379
- 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;
16143
+ 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;
15380
16144
  const withoutTonic = (pool, root) => {
15381
- const isTonic = (c) => root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
16145
+ const isTonic = (c) => center ? center.tonicPattern.test(c) : root === "Am" ? /^Am/.test(c) : /^C(?![#b]|m)/.test(c);
15382
16146
  const out = pool.filter((p) => !p.some(isTonic));
15383
16147
  return out.length > 0 ? out : pool;
15384
16148
  };
15385
- const homeRoot = resolvedKey.mode === "minor" ? "Am" : "C";
16149
+ const homeRoot = center ? center.tonic : resolvedKey.mode === "minor" ? "Am" : "C";
15386
16150
  const progA = pick(
15387
16151
  floating ? withoutTonic(progAPool, homeRoot) : progAPool,
15388
16152
  rnd
15389
16153
  );
15390
- const progBPool = SECTION_B_PROGRESSIONS.filter(
16154
+ const progBPool = (center ? center.b : SECTION_B_PROGRESSIONS).filter(
15391
16155
  (p) => p.join("|") !== progA.join("|")
15392
16156
  );
15393
16157
  const progB = pick(
15394
16158
  floating ? withoutTonic(progBPool, homeRoot) : progBPool,
15395
16159
  rnd
15396
16160
  );
15397
- const progRelativePool = SECTION_A_PROGRESSIONS.filter(
16161
+ const progRelativePool = center ? center.a : SECTION_A_PROGRESSIONS.filter(
15398
16162
  (p) => resolvedKey.mode === "minor" ? !p[0].startsWith("Am") : p[0].startsWith("Am")
15399
16163
  );
15400
16164
  const progRelative = pick(
15401
16165
  floating ? withoutTonic(progRelativePool, homeRoot) : progRelativePool,
15402
16166
  rnd
15403
16167
  );
15404
- const progCPool = SECTION_C_PROGRESSIONS.filter(
16168
+ const progCPool = (center ? center.c : SECTION_C_PROGRESSIONS).filter(
15405
16169
  (p) => p.join("|") !== progA.join("|") && p.join("|") !== progB.join("|")
15406
16170
  );
15407
16171
  const progC = pick(
15408
16172
  floating ? withoutTonic(progCPool, homeRoot) : progCPool,
15409
16173
  rnd
15410
16174
  );
15411
- const tonic = progA[0].startsWith("Am") ? "Am" : "C";
15412
- const progHalf = pick(SECTION_A2_DERIVATIONS, rnd)(progA);
15413
- const progFull = pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
15414
- const progDeceptive = pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
16175
+ const tonic = center ? center.tonic : progA[0].startsWith("Am") ? "Am" : "C";
16176
+ const progHalf = center ? pick(MODAL_HALF_DERIVATIONS, rnd)(progA, center) : pick(SECTION_A2_DERIVATIONS, rnd)(progA);
16177
+ const progFull = center ? pick(MODAL_FULL_DERIVATIONS, rnd)(progA, center) : pick(SECTION_A3_DERIVATIONS, rnd)(progA, tonic);
16178
+ const progDeceptive = center ? pick(MODAL_DECEPTIVE_DERIVATIONS, rnd)(progA, center) : pick(SECTION_DECEPTIVE_DERIVATIONS, rnd)(progA, tonic);
15415
16179
  let lastChorusBar = -1;
15416
16180
  for (const section of sectionPlan)
15417
16181
  if (section.kind === "chorus" || section.kind === "outro")
@@ -15458,7 +16222,7 @@ var draw = (options, resolvedKey, rnd) => {
15458
16222
  progression[bar - 1] = transposeChordName("G7", shift - prev);
15459
16223
  }
15460
16224
  }
15461
- if (rnd() < 0.3) {
16225
+ if (rnd() < 0.3 && !center) {
15462
16226
  const tonicNames = tonic === "Am" ? ["Am", "Am7"] : ["C", "CM7"];
15463
16227
  const spots = [];
15464
16228
  for (let bar = 0; bar + 1 < totalBars; bar++) {
@@ -15479,21 +16243,27 @@ var draw = (options, resolvedKey, rnd) => {
15479
16243
  const sourceOf = (kind) => kind === "chorus" || kind === "interlude" || kind === "drop_chorus" ? "b" : kind === "prechorus" ? "a2" : kind === "bridge" ? "c" : "a";
15480
16244
  const landingOf = (section) => {
15481
16245
  if (section.spec.landing === null) return null;
15482
- const minorHere = resolvedKey.mode === "minor" !== relativeKinds.has(section.kind);
15483
- let landing = (section.spec.landing + (minorHere ? 5 : 0)) % 7;
15484
- if (floating && landing === (minorHere ? 5 : 0))
15485
- landing = (landing + pick([2, 4], rnd)) % 7;
16246
+ const relativeHere = relativeKinds.has(section.kind);
16247
+ const tonicDegree = relativeHere ? scale.tonic === 5 ? 0 : 5 : scale.tonic;
16248
+ const size = scaleSize(scale);
16249
+ let landing = (section.spec.landing + tonicDegree) % size;
16250
+ if (floating && landing === tonicDegree)
16251
+ landing = (landing + pick([2, 4], rnd)) % size;
15486
16252
  return landing;
15487
16253
  };
16254
+ const form = resolveMelodyForm(options.form, rnd);
15488
16255
  const units2 = [];
15489
16256
  for (const section of sectionPlan) {
15490
16257
  const unitCount = Math.max(1, Math.round(section.bars / 2));
15491
16258
  const src = sourceOf(section.kind);
15492
16259
  for (let u = 0; u < unitCount; u++) {
15493
16260
  if (!section.spec.melody) {
16261
+ const solo2 = section.kind === "interlude";
15494
16262
  units2.push({
15495
- role: "hold",
15496
- source: "silent",
16263
+ // 見せ場なので走句と山を交互に置く。`hold` のままだと
16264
+ // リズム型が最も薄いものになり、ソロにならない。
16265
+ role: solo2 ? u % 2 === 0 ? "run" : "climax" : "hold",
16266
+ source: solo2 ? "solo" : "silent",
15497
16267
  landing: null,
15498
16268
  section
15499
16269
  });
@@ -15502,16 +16272,31 @@ var draw = (options, resolvedKey, rnd) => {
15502
16272
  const isLast = u === unitCount - 1;
15503
16273
  if (u % 2 === 0) {
15504
16274
  units2.push({
15505
- role: section.kind === "bridge" ? "step" : section.kind === "chorus" ? "climax" : src === "a2" || u > 0 ? "sequence" : "motif",
15506
- source: src,
16275
+ // リフ主体の曲は**変形しない**。セクエンツもオクターブ上げも入れず、
16276
+ // 同じ型を回し続ける。セクションの対比は編曲側(ドラム・楽器・レイヤ)
16277
+ // が担う——ヤツメ穴型の曲がまさにその作りで、120小節を通して
16278
+ // 5半音のセルが変わらない。
16279
+ role: form === "ostinato" ? "motif" : form === "through" ? (
16280
+ // 通し作曲は素材を戻さない。`step` の書法(アーチ・谷・波)で
16281
+ // 独立した線を書き、たまに走句と山を挟んで単調さを避ける。
16282
+ u % 3 === 2 ? "run" : u % 3 === 1 ? "climax" : "step"
16283
+ ) : section.kind === "bridge" ? "step" : section.kind === "chorus" ? "climax" : src === "a2" || u > 0 ? "sequence" : "motif",
16284
+ // リフ型は素材も1つに揃える(`sourceOf` でセクションごとに
16285
+ // 変えると、そこだけ別の型が始まってオスティナートにならない)。
16286
+ source: form === "ostinato" ? "a" : src,
15507
16287
  landing: null,
15508
16288
  section
15509
16289
  });
15510
16290
  } else {
15511
16291
  const landing = landingOf(section);
16292
+ const riff = form === "ostinato" && !(isLast && landing === 0);
15512
16293
  units2.push({
15513
- role: isLast && landing === 0 ? "cadence" : "answer",
15514
- source: "answer",
16294
+ role: isLast && landing === 0 ? "cadence" : riff ? "motif" : (
16295
+ // 通し作曲は「問いと答え」で閉じない。answer は問いのリズムを
16296
+ // 受けて着地音だけ変える形なので、そのままだと反復が戻る。
16297
+ form === "through" ? "step" : "answer"
16298
+ ),
16299
+ source: riff ? "a" : "answer",
15515
16300
  landing: isLast ? landing : null,
15516
16301
  section
15517
16302
  });
@@ -15529,6 +16314,7 @@ var draw = (options, resolvedKey, rnd) => {
15529
16314
  const restatementOf = (bar) => {
15530
16315
  const curSec = sectionAt(sectionPlan, bar);
15531
16316
  if (!curSec.spec.melody) return null;
16317
+ if (form === "through") return null;
15532
16318
  if (curSec.restatement) {
15533
16319
  const firstSec = sectionPlan.find(
15534
16320
  (s) => s.kind === curSec.kind && !s.restatement
@@ -15550,14 +16336,19 @@ var draw = (options, resolvedKey, rnd) => {
15550
16336
  }
15551
16337
  return null;
15552
16338
  };
16339
+ const registerSpread = form === "ostinato" ? rnd() ** 2 * 0.5 : 0.25 + rnd() * 0.75;
15553
16340
  const style = {
15554
16341
  groove: pick(["eighth", "sixteenth"], rnd),
15555
16342
  arcPeriod: pick([4, 8, 8, 16], rnd),
15556
16343
  arcPhase: pick([0, 1, 2], rnd),
15557
- arcAmp: 2 + rnd() * 3,
16344
+ arcAmp: 5 * registerSpread,
15558
16345
  // オクターブ跳躍は参考曲では音程の1.0%しかない。上げすぎると音域が広がる。
15559
- octaveAffinity: 0.06 + rnd() * 0.12,
15560
- pentatonicMotif: rnd() < 0.55,
16346
+ octaveAffinity: 0.18 * registerSpread,
16347
+ maxLeap: pick(LEAP_CEILINGS, rnd),
16348
+ // **音階を厳しく締める曲は必ず中核音の歩数で組む。** ダイアトニックの度数で輪郭を
16349
+ // 作ると、琉球音階なのにレやラが輪郭の中に入り込む。ファ・シを自由に使う
16350
+ // 陽・民謡だけが、曲ごとに掛けたり掛けなかったりする({@link ComposeScale.strict})。
16351
+ pentatonicMotif: rnd() < 0.55 || scale.strict,
15561
16352
  runShape: pick(["scale", "turn", "broken", "zigzag"], rnd),
15562
16353
  stepShape: pick(
15563
16354
  ["arch", "valley", "ascend", "descend", "wave", "pivot"],
@@ -15575,7 +16366,11 @@ var draw = (options, resolvedKey, rnd) => {
15575
16366
  // 界隈曲らしさ:調の外の音(クロマチック)や微小な逸脱を積極的に許容する。
15576
16367
  // 刻みを細かくすると経過音の置き場所が増えるので、同じ係数でも変化音は増える。
15577
16368
  // 参考曲の1.75倍まで伸びていたぶんを引く。
15578
- chromaticAffinity: rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33,
16369
+ // **音階を厳しく締める曲は変化音を控える。** 半音の経過音は長調・短調の泣きメロの
16370
+ // 芯だが、琉球・都節・律ではその半音が音階の外にしか無く、入れたぶんだけ
16371
+ // 音階の色が薄まる(実測で嬰ヘが3%出て、音階内のラ2%より多いという逆転が
16372
+ // 起きていた)。0 にはしない——民族音階の実際の曲にも装飾の半音は出る。
16373
+ chromaticAffinity: (rnd() < 0.2 ? 0 : 0.12 + rnd() * 0.33) * (scale.strict ? 0.4 : 1),
15579
16374
  barHeadWeight: rnd() < 0.5 ? 3 : 2,
15580
16375
  bassStyle: pick(
15581
16376
  [
@@ -15602,8 +16397,11 @@ var draw = (options, resolvedKey, rnd) => {
15602
16397
  subInterval: pick([3, 4, 8, 9], rnd)
15603
16398
  };
15604
16399
  const motifPool = groovyCells(MOTIF_CELLS, style.groove, rnd);
15605
- const targetNotesPerBar = 4.5 + rnd() * 4.5;
15606
- const targetRestRatio = 0.02 + rnd() * 0.1;
16400
+ const targetRestRatio = 0.02 + rnd() ** 2 * 0.42;
16401
+ const targetNotesPerBar = Math.max(
16402
+ 2.8,
16403
+ Math.min(9.5, 7.4 - 6 * targetRestRatio + (rnd() * 4 - 2))
16404
+ );
15607
16405
  const cellNotes = (c) => c.value.filter((v) => v > 0).length;
15608
16406
  const cellRest = (c) => {
15609
16407
  let rest = 0;
@@ -15626,9 +16424,13 @@ var draw = (options, resolvedKey, rnd) => {
15626
16424
  return pool[pool.length - 1];
15627
16425
  };
15628
16426
  const pickCell = (pool, densityMul = 1) => {
15629
- let best = weightedPick(pool);
16427
+ const want = targetRestRatio / Math.max(0.5, densityMul);
16428
+ const tol = Math.max(0.08, want * 0.6);
16429
+ const near = pool.filter((c) => Math.abs(cellRest(c) - want) <= tol);
16430
+ const from = near.length > 0 ? near : pool;
16431
+ let best = weightedPick(from);
15630
16432
  for (let i2 = 0; i2 < 2; i2++) {
15631
- const c = weightedPick(pool);
16433
+ const c = weightedPick(from);
15632
16434
  if (cellDistance(c, densityMul) < cellDistance(best, densityMul))
15633
16435
  best = c;
15634
16436
  }
@@ -15730,7 +16532,7 @@ var draw = (options, resolvedKey, rnd) => {
15730
16532
  if (breathBars.has(source)) breathBars.add(bar);
15731
16533
  continue;
15732
16534
  }
15733
- const isB = units2[u].source === "b";
16535
+ const isB = units2[u].source === "b" || units2[u].source === "solo";
15734
16536
  const isA2 = units2[u].source === "a2";
15735
16537
  const isC = units2[u].source === "c";
15736
16538
  const isPrechorusEnd = units2[u].section.kind === "prechorus" && bar === units2[u].section.startBar + units2[u].section.bars - 1;
@@ -15780,6 +16582,7 @@ var draw = (options, resolvedKey, rnd) => {
15780
16582
  const harmony = [];
15781
16583
  const harmony2 = [];
15782
16584
  const pad = [];
16585
+ const solo = [];
15783
16586
  const melodyDurations = [];
15784
16587
  const barTension = new Array(totalBars).fill(0);
15785
16588
  let restSteps = 0;
@@ -15790,9 +16593,9 @@ var draw = (options, resolvedKey, rnd) => {
15790
16593
  let chromaticNotes = 0;
15791
16594
  let sungBars = 0;
15792
16595
  let prevSemi = pick([60, 64, 65, 67, 69, 72, 74, 76], rnd);
15793
- const pickFigure = (gapSteps, scale) => {
16596
+ const pickFigure = (gapSteps, scale2) => {
15794
16597
  const fits = ANSWER_FIGURES.filter(
15795
- (f) => f.reduce((sum, v) => sum + scale(Math.abs(v)), 0) <= gapSteps
16598
+ (f) => f.reduce((sum, v) => sum + scale2(Math.abs(v)), 0) <= gapSteps
15796
16599
  );
15797
16600
  return fits.length === 0 ? null : pick(fits, rnd);
15798
16601
  };
@@ -15850,8 +16653,9 @@ var draw = (options, resolvedKey, rnd) => {
15850
16653
  slots,
15851
16654
  tones,
15852
16655
  style,
16656
+ scale,
15853
16657
  motifContour,
15854
- semitoneToDegree(headSemi),
16658
+ semitoneToDegree(scale, headSemi),
15855
16659
  contourOffset,
15856
16660
  repeatShift,
15857
16661
  headWeight,
@@ -15867,7 +16671,8 @@ var draw = (options, resolvedKey, rnd) => {
15867
16671
  ...plannedDegrees[source]
15868
16672
  );
15869
16673
  const landing = units2[unitOf(bar)].landing;
15870
- if (landing !== null && barInUnit(bar) === 1) landOn(degrees, landing);
16674
+ if (landing !== null && barInUnit(bar) === 1)
16675
+ landOn(scale, degrees, landing);
15871
16676
  plannedDegrees[bar] = [...degrees];
15872
16677
  const isMotifBar = source !== null || role === "motif" || role === "sequence" || role === "climax" || role === "answer";
15873
16678
  let fitted = degrees;
@@ -15879,13 +16684,18 @@ var draw = (options, resolvedKey, rnd) => {
15879
16684
  tones,
15880
16685
  prevSemi,
15881
16686
  quarterSteps,
16687
+ scale,
15882
16688
  style.pentatonicMotif,
15883
- motifShiftMemo.get(shiftKey) ?? null
16689
+ motifShiftMemo.get(shiftKey) ?? null,
16690
+ // リフ型は和音へ寄せない。同じセルを回し続けるのが役目。
16691
+ form === "ostinato" ? 0 : 3
15884
16692
  );
15885
16693
  fitted = r.degrees;
15886
16694
  motifShiftMemo.set(shiftKey, r.shift);
15887
16695
  }
15888
16696
  const pitches = shapeBar(fitted, slots, tones, prevSemi, {
16697
+ scale,
16698
+ maxLeap: style.maxLeap,
15889
16699
  allowLeap: role === "climax",
15890
16700
  allowArpeggio: role === "climax" || role === "run" && style.runShape === "broken" || role === "cadence" && style.cadenceShape !== "descend",
15891
16701
  quarterSteps,
@@ -15900,16 +16710,19 @@ var draw = (options, resolvedKey, rnd) => {
15900
16710
  rnd,
15901
16711
  preserveContour: isMotifBar
15902
16712
  });
15903
- if (landing !== null && barInUnit(bar) === 1) landPitch(pitches, landing);
15904
- const fifths = pitches.map(diatonicFifth);
15905
- applyChromatic(pitches, fifths, slots, tones, {
16713
+ if (landing !== null && barInUnit(bar) === 1)
16714
+ landPitch(scale, pitches, landing);
16715
+ const fifths = pitches.map((semi) => scaleFifth(scale, semi));
16716
+ applyChromatic(scale, pitches, fifths, slots, tones, {
15906
16717
  affinity: style.chromaticAffinity,
15907
16718
  quarterSteps,
15908
16719
  shortSteps: scaleStep(EIGHTH),
15909
16720
  keepLast: landing !== null && barInUnit(bar) === 1,
15910
16721
  rnd
15911
16722
  });
15912
- const silent = units2[unitOf(bar)].source === "silent";
16723
+ const unitSource = units2[unitOf(bar)].source;
16724
+ const isSolo = unitSource === "solo";
16725
+ const silent = unitSource === "silent" || isSolo;
15913
16726
  const barHead = pitches[0];
15914
16727
  let tensionSum = 0;
15915
16728
  let tensionSteps = 0;
@@ -15917,7 +16730,7 @@ var draw = (options, resolvedKey, rnd) => {
15917
16730
  const semi = pitches[i2];
15918
16731
  tensionSum += (3 - toneWeight(semi, tones)) / 3 * slots[i2].value;
15919
16732
  tensionSteps += slots[i2].value;
15920
- if (role !== "climax") {
16733
+ if (!silent && role !== "climax") {
15921
16734
  const gap = Math.abs(semi - prevSemi);
15922
16735
  maxLeap = Math.max(maxLeap, gap);
15923
16736
  intervals++;
@@ -15926,10 +16739,25 @@ var draw = (options, resolvedKey, rnd) => {
15926
16739
  }
15927
16740
  const slot = slots[i2];
15928
16741
  if (silent) {
16742
+ if (isSolo) {
16743
+ const ks = barKeyShift[bar];
16744
+ const fifthShiftSolo = ks === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(ks % 12 + 12) % 12];
16745
+ solo.push({
16746
+ startStep: barStart + slot.at,
16747
+ pitchUnits: spelledToUnits(
16748
+ semi + ks,
16749
+ fifths[i2] + fifthShiftSolo,
16750
+ edo
16751
+ ),
16752
+ durationSteps: slot.value,
16753
+ // ソロは前に出る声部なので、歌メロより気持ち強く弾く。
16754
+ velocity: slot.at === 0 ? 116 : slot.value <= scaleStep(SIXTEENTH) ? 96 : 106
16755
+ });
16756
+ }
15929
16757
  prevSemi = semi;
15930
16758
  continue;
15931
16759
  }
15932
- if (!DIATONIC_PCS.has(pitchClass(semi))) chromaticNotes++;
16760
+ if (!scalePcs(scale).has(pitchClass(semi))) chromaticNotes++;
15933
16761
  const k = barKeyShift[bar];
15934
16762
  const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
15935
16763
  melody.push({
@@ -15983,7 +16811,7 @@ var draw = (options, resolvedKey, rnd) => {
15983
16811
  subSemi = pushSub(
15984
16812
  cursor,
15985
16813
  step,
15986
- i2 === 0 ? subSemi : walk(subSemi, subDir)
16814
+ i2 === 0 ? subSemi : walk(scale, subSemi, subDir)
15987
16815
  );
15988
16816
  placed++;
15989
16817
  }
@@ -16007,7 +16835,7 @@ var draw = (options, resolvedKey, rnd) => {
16007
16835
  subSemi = pushSub(
16008
16836
  cursor,
16009
16837
  step,
16010
- index === 0 ? subSemi : walk(subSemi, subDir * (index % 2 === 0 ? 1 : -1))
16838
+ index === 0 ? subSemi : walk(scale, subSemi, subDir * (index % 2 === 0 ? 1 : -1))
16011
16839
  );
16012
16840
  index++;
16013
16841
  }
@@ -16017,7 +16845,11 @@ var draw = (options, resolvedKey, rnd) => {
16017
16845
  pushSub(0, stepsPerBar, subSemi);
16018
16846
  } else if (subStyle === "long-short") {
16019
16847
  subSemi = pushSub(0, scaleStep(DOT_HALF), subSemi);
16020
- pushSub(scaleStep(DOT_HALF), scaleStep(QUARTER), walk(subSemi, subDir));
16848
+ pushSub(
16849
+ scaleStep(DOT_HALF),
16850
+ scaleStep(QUARTER),
16851
+ walk(scale, subSemi, subDir)
16852
+ );
16021
16853
  } else if (role === "cadence") {
16022
16854
  pushSub(0, stepsPerBar, subSemi);
16023
16855
  } else {
@@ -16032,6 +16864,7 @@ var draw = (options, resolvedKey, rnd) => {
16032
16864
  const written = [];
16033
16865
  for (let i2 = 0; i2 < slots.length; i2++) {
16034
16866
  const hTone = harmonyPitch(
16867
+ scale,
16035
16868
  pitches[i2],
16036
16869
  tones,
16037
16870
  last,
@@ -16102,6 +16935,7 @@ var draw = (options, resolvedKey, rnd) => {
16102
16935
  const nextTones = chordTones(progression[(bar + 1) % totalBars]);
16103
16936
  const A = clampSemi(
16104
16937
  walk(
16938
+ scale,
16105
16939
  nextTones[0] ? clampSemi(nextTones[0].semi, BASS_LOW, BASS_HIGH) : R,
16106
16940
  -1
16107
16941
  ),
@@ -16158,7 +16992,7 @@ var draw = (options, resolvedKey, rnd) => {
16158
16992
  let bassCursor = 0;
16159
16993
  for (const [semi, value] of bassCell) {
16160
16994
  const len = scaleStep(value);
16161
- const fifth = semi === R || semi === O ? rootTone.fifth : semi === F ? fifthTone?.fifth ?? rootTone.fifth : semi === T ? thirdTone?.fifth ?? rootTone.fifth : degreeToPitch(semitoneToDegree(semi)).fifth;
16995
+ const fifth = semi === R || semi === O ? rootTone.fifth : semi === F ? fifthTone?.fifth ?? rootTone.fifth : semi === T ? thirdTone?.fifth ?? rootTone.fifth : scaleFifth(scale, semi);
16162
16996
  const k = barKeyShift[bar];
16163
16997
  const fifthShift = k === 0 ? 0 : SEMITONE_TO_FIFTH_SHIFT[(k % 12 + 12) % 12];
16164
16998
  barBass.push({
@@ -16220,7 +17054,7 @@ var draw = (options, resolvedKey, rnd) => {
16220
17054
  const after = melody[i2 + 2];
16221
17055
  if (after && Math.floor(after.startStep / stepsPerBar) === barIdx) {
16222
17056
  const afterSemi = Math.round(after.pitchUnits / UNITS_PER_SEMITONE) - (barKeyShift[barIdx] ?? 0);
16223
- if (!DIATONIC_PCS.has(pitchClass(afterSemi))) continue;
17057
+ if (!scalePcs(scale).has(pitchClass(afterSemi))) continue;
16224
17058
  }
16225
17059
  const nextRole = barRoles[barIdx];
16226
17060
  let tieProb = 0.3;
@@ -16241,11 +17075,13 @@ var draw = (options, resolvedKey, rnd) => {
16241
17075
  melodyDurations[i2] += melodyDurations[i2 + 1];
16242
17076
  melodyDurations.splice(i2 + 1, 1);
16243
17077
  }
16244
- const hCurIdx = harmony.findIndex((h) => h.startStep === cur.startStep);
16245
- const hNxtIdx = harmony.findIndex((h) => h.startStep === nxt.startStep);
16246
- if (hCurIdx >= 0 && hNxtIdx >= 0) {
16247
- harmony[hCurIdx].durationSteps += harmony[hNxtIdx].durationSteps;
16248
- harmony.splice(hNxtIdx, 1);
17078
+ for (const voice of [harmony, harmony2]) {
17079
+ const nxtIdx = voice.findIndex((h) => h.startStep === nxt.startStep);
17080
+ if (nxtIdx < 0) continue;
17081
+ const curIdx = voice.findIndex((h) => h.startStep === cur.startStep);
17082
+ if (curIdx >= 0)
17083
+ voice[curIdx].durationSteps += voice[nxtIdx].durationSteps;
17084
+ voice.splice(nxtIdx, 1);
16249
17085
  }
16250
17086
  i2--;
16251
17087
  }
@@ -16299,17 +17135,20 @@ var draw = (options, resolvedKey, rnd) => {
16299
17135
  };
16300
17136
  const shiftUnits = semitonesToUnits(rootShift, edo);
16301
17137
  if (shiftUnits !== 0)
16302
- for (const list of [melody, submelody, bass, harmony, harmony2, pad])
17138
+ for (const list of [melody, submelody, bass, harmony, harmony2, pad, solo])
16303
17139
  for (const n of list) n.pitchUnits = n.pitchUnits + shiftUnits;
16304
17140
  const octave = useOctaveLayer ? melody.filter(
16305
17141
  (n) => n.durationSteps >= quarterSteps || rnd() < octaveCoverage
16306
17142
  ).map((n) => ({ ...n, velocity: Math.max(40, n.velocity - 26) })) : [];
16307
17143
  return {
17144
+ form,
16308
17145
  chordProgression,
16309
17146
  chordPattern,
16310
17147
  rootShift,
16311
17148
  keyName: resolvedKey.keyName,
16312
17149
  keyLabel: resolvedKey.keyLabel,
17150
+ scaleId: scale.id,
17151
+ scaleLabel: scale.label,
16313
17152
  moodLabel: resolvedKey.moodLabel,
16314
17153
  bpm,
16315
17154
  sections: sectionPlan,
@@ -16329,6 +17168,7 @@ var draw = (options, resolvedKey, rnd) => {
16329
17168
  harmony2,
16330
17169
  octave,
16331
17170
  pad,
17171
+ solo,
16332
17172
  melodyDurations,
16333
17173
  restSteps,
16334
17174
  totalSteps: Math.max(1, sungBars) * stepsPerBar,
@@ -16388,7 +17228,7 @@ var evaluate = (d, recent) => {
16388
17228
  Math.min(...recent.map((r) => featureDistance(fingerprint, r))) / 1
16389
17229
  );
16390
17230
  const at = (b, v) => band(v, b[0], b[1], b[2], b[3]);
16391
- const atc = (key, v) => centeredBand(v, CORPUS_BANDS[key], CORPUS_MEDIANS[key]);
17231
+ const atc = (key, v) => plausibleBand(v, CORPUS_BANDS[key]);
16392
17232
  const peakBand = d.bars > 24 ? [0, 1, Math.round(d.bars / 16), Math.round(d.bars / 8) + 2] : HAND_BANDS.climaxPeaks;
16393
17233
  const scoreBreakdown = {
16394
17234
  entropy: atc("entropy", entropy),
@@ -16424,9 +17264,16 @@ var evaluate = (d, recent) => {
16424
17264
  tensionResolve: d.tonal.floating ? 1 : tension.resolve,
16425
17265
  novelty
16426
17266
  };
17267
+ const forgiven = new Set(
17268
+ Object.entries(WEIGHTS).filter(([key]) => BUDGETED_KEYS.has(key)).map(([key, weight]) => ({
17269
+ key,
17270
+ deficit: weight * (1 - (scoreBreakdown[key] ?? 0))
17271
+ })).sort((a, b) => b.deficit - a.deficit).slice(0, DEVIATION_BUDGET).filter((e) => e.deficit > 0).map((e) => e.key)
17272
+ );
16427
17273
  let weighted = 0;
16428
17274
  let weightSum = 0;
16429
17275
  for (const [key, weight] of Object.entries(WEIGHTS)) {
17276
+ if (forgiven.has(key)) continue;
16430
17277
  weighted += (scoreBreakdown[key] ?? 0) * weight;
16431
17278
  weightSum += weight;
16432
17279
  }
@@ -16458,49 +17305,74 @@ var composeSong = (options) => {
16458
17305
  const recent = options.recent ?? [];
16459
17306
  const count = Math.max(1, options.drawCount ?? DRAW_COUNT);
16460
17307
  const resolvedKey = resolveComposeKey(options.baseKey, rnd);
16461
- let best = null;
16462
- let bestScore = Number.NEGATIVE_INFINITY;
16463
- let bestIsValid = false;
17308
+ const scale = resolveComposeScale(
17309
+ options.scale,
17310
+ resolvedKey.mode === "minor",
17311
+ rnd
17312
+ );
17313
+ const valid = [];
17314
+ const invalid = [];
16464
17315
  let rejected = 0;
16465
17316
  for (let attempt = 1; attempt <= count; attempt++) {
16466
- const d = draw(options, resolvedKey, rnd);
16467
- const { stats, ok } = evaluate(d, recent);
16468
- if (!ok) rejected++;
16469
- const better = ok === bestIsValid ? stats.score > bestScore : ok;
16470
- if (!better) continue;
16471
- bestScore = stats.score;
16472
- bestIsValid = ok;
16473
- best = {
16474
- // ドラム・楽器は勝った候補にだけ後から付ける(メロディに依存しないので
16475
- // 候補ごとに引いても採点は動かず、40本ぶん無駄になる)。
16476
- drum: "",
16477
- instrument: "",
16478
- chordProgression: d.chordProgression,
16479
- chordPattern: d.chordPattern,
16480
- rootShift: d.rootShift,
16481
- keyName: d.keyName,
16482
- keyLabel: d.keyLabel,
16483
- moodLabel: d.moodLabel,
16484
- bpm: d.bpm,
16485
- sections: d.sections,
16486
- bars: d.bars,
16487
- vocal: d.vocal,
16488
- tonal: d.tonal,
16489
- melody: d.melody,
16490
- submelody: d.submelody,
16491
- bass: d.bass,
16492
- harmony: d.harmony,
16493
- harmony2: d.harmony2,
16494
- octave: d.octave,
16495
- pad: d.pad,
16496
- stats: { ...stats, attempts: attempt, rejected }
16497
- };
17317
+ const d2 = draw(options, resolvedKey, scale, rnd);
17318
+ const { stats, ok } = evaluate(d2, recent);
17319
+ if (ok) valid.push({ d: d2, stats });
17320
+ else {
17321
+ rejected++;
17322
+ invalid.push({ d: d2, stats });
17323
+ }
17324
+ }
17325
+ const pool = valid.length > 0 ? valid : invalid;
17326
+ const top = Math.max(...pool.map((c) => c.stats.score));
17327
+ const weights = pool.map(
17328
+ (c) => Math.exp((c.stats.score - top) / SELECT_TEMPERATURE)
17329
+ );
17330
+ const total = weights.reduce((a, b) => a + b, 0);
17331
+ let ticket = rnd() * total;
17332
+ let chosen = pool[pool.length - 1];
17333
+ for (let i2 = 0; i2 < pool.length; i2++) {
17334
+ ticket -= weights[i2];
17335
+ if (ticket <= 0) {
17336
+ chosen = pool[i2];
17337
+ break;
17338
+ }
16498
17339
  }
16499
- const result = best;
17340
+ const d = chosen.d;
17341
+ const result = {
17342
+ // ドラム・楽器・編曲プランは勝った候補にだけ後から付ける(メロディに
17343
+ // 依存しないので候補ごとに引いても採点は動かず、候補数ぶん無駄になる)。
17344
+ drum: "",
17345
+ instrument: "",
17346
+ arrange: EMPTY_ARRANGE,
17347
+ chordProgression: d.chordProgression,
17348
+ chordPattern: d.chordPattern,
17349
+ rootShift: d.rootShift,
17350
+ keyName: d.keyName,
17351
+ keyLabel: d.keyLabel,
17352
+ scaleId: scale.id,
17353
+ scaleLabel: scale.label,
17354
+ form: d.form,
17355
+ moodLabel: d.moodLabel,
17356
+ bpm: d.bpm,
17357
+ sections: d.sections,
17358
+ bars: d.bars,
17359
+ vocal: d.vocal,
17360
+ tonal: d.tonal,
17361
+ melody: d.melody,
17362
+ submelody: d.submelody,
17363
+ bass: d.bass,
17364
+ harmony: d.harmony,
17365
+ harmony2: d.harmony2,
17366
+ octave: d.octave,
17367
+ pad: d.pad,
17368
+ solo: d.solo,
17369
+ stats: { ...chosen.stats, attempts: count, rejected }
17370
+ };
16500
17371
  result.stats.attempts = count;
16501
17372
  result.stats.rejected = rejected;
16502
17373
  result.drum = pickBuiltinDrum(result, rnd);
16503
17374
  result.instrument = pickBuiltinInstrument(result, rnd);
17375
+ result.arrange = buildArrangePlan(result, rnd);
16504
17376
  return result;
16505
17377
  };
16506
17378
  var pickBuiltinDrum = (song, rnd) => {
@@ -16512,6 +17384,80 @@ var pickBuiltinDrum = (song, rnd) => {
16512
17384
  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"];
16513
17385
  return pick(pool, rnd);
16514
17386
  };
17387
+ var EMPTY_ARRANGE = {
17388
+ backing: [],
17389
+ sparkle: null,
17390
+ padSections: [],
17391
+ lead: null,
17392
+ bassLayer: null
17393
+ };
17394
+ var LOUD_KINDS = ["prechorus", "chorus", "bridge"];
17395
+ var QUIET_KINDS = [
17396
+ "intro",
17397
+ "verse",
17398
+ "interlude",
17399
+ "drop_chorus",
17400
+ "outro"
17401
+ ];
17402
+ var CHORUS_KINDS = ["chorus", "drop_chorus"];
17403
+ var buildArrangePlan = (song, rnd) => {
17404
+ const present = new Set(song.sections.map((s) => s.kind));
17405
+ const narrow = (kinds) => {
17406
+ const hit = kinds.filter((k) => present.has(k));
17407
+ return hit.length === 0 ? null : hit;
17408
+ };
17409
+ const base = {
17410
+ pattern: song.chordPattern,
17411
+ sections: null,
17412
+ octave: 0
17413
+ };
17414
+ const others = chordPatternPool(song.bpm).filter((p) => p !== base.pattern);
17415
+ const backing = [base];
17416
+ const addKinds = pick(
17417
+ [LOUD_KINDS, CHORUS_KINDS, LOUD_KINDS, QUIET_KINDS],
17418
+ rnd
17419
+ );
17420
+ const second = pick(others, rnd);
17421
+ backing.push({
17422
+ pattern: second,
17423
+ sections: narrow(addKinds),
17424
+ octave: 0
17425
+ });
17426
+ if (rnd() < 0.55) {
17427
+ const rest = others.filter((p) => p !== second);
17428
+ backing.push({
17429
+ pattern: pick(rest.length > 0 ? rest : others, rnd),
17430
+ // 2本目と逆側へ置く(両方サビに寄せると、サビだけ団子になる)。
17431
+ sections: narrow(addKinds === QUIET_KINDS ? LOUD_KINDS : QUIET_KINDS),
17432
+ octave: 0
17433
+ });
17434
+ }
17435
+ const sparklePool = chordPatternPool(song.bpm).filter(
17436
+ (p) => p !== "block" && !backing.some((b) => b.pattern === p)
17437
+ );
17438
+ const sparkle = sparklePool.length > 0 && rnd() < 0.7 ? {
17439
+ pattern: pick(sparklePool, rnd),
17440
+ sections: narrow(pick([LOUD_KINDS, CHORUS_KINDS], rnd)) ?? CHORUS_KINDS,
17441
+ octave: 1
17442
+ } : null;
17443
+ const padSections = narrow(
17444
+ pick(
17445
+ [
17446
+ ["prechorus", "chorus", "bridge", "drop_chorus"],
17447
+ ["chorus", "drop_chorus"],
17448
+ ["prechorus", "chorus", "bridge", "drop_chorus"],
17449
+ ["bridge", "chorus"]
17450
+ ],
17451
+ rnd
17452
+ )
17453
+ ) ?? [];
17454
+ const lead = rnd() < 0.75 ? {
17455
+ sections: narrow(pick([CHORUS_KINDS, LOUD_KINDS], rnd)) ?? CHORUS_KINDS,
17456
+ octave: rnd() < 0.5 ? 0 : 1
17457
+ } : null;
17458
+ const bassLayer = rnd() < 0.25 ? { sections: narrow(CHORUS_KINDS) ?? CHORUS_KINDS, octave: 1 } : null;
17459
+ return { backing, sparkle, padSections, lead, bassLayer };
17460
+ };
16515
17461
  var pickBuiltinInstrument = (song, rnd) => {
16516
17462
  const eighth = BASE_STEPS_PER_BAR / 8;
16517
17463
  const short = song.melody.filter((n) => n.durationSteps <= eighth).length / Math.max(1, song.melody.length);
@@ -16622,6 +17568,492 @@ var composeLyrics = (melody, options) => {
16622
17568
  return out.join("");
16623
17569
  };
16624
17570
 
17571
+ // src/instrument-presets.ts
17572
+ var INSTRUMENT_PRESETS = {
17573
+ // --- STANDARD: 汎用性と完成度重視 ---
17574
+ piano: {
17575
+ displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
17576
+ 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",
17577
+ melody: "Acoustic Grand Piano",
17578
+ submelody: "Vibraphone",
17579
+ bass: "Electric Bass (finger)",
17580
+ chord: "Pad 2 (warm)",
17581
+ solo: "Electric Guitar (clean)",
17582
+ // **Glockenspiel は使わない。音源側のサンプルが音程を外している**(実聴で確認)。
17583
+ // 音域の問題(G5〜の高音楽器)なら {@link GM_BRIGHT_CEILING} で下げれば済むが、
17584
+ // ピッチそのものがずれているものは置き場所を変えても直らない。同じ役割
17585
+ // (サビの上に乗る明るい音板)で、素直に鳴る Celesta へ差し替えてある。
17586
+ // ※ {@link GM_INSTRUMENT_RANGE} / {@link GM_BRIGHT_CEILING} の Glockenspiel の
17587
+ // 項は残す。手で選んだときの置き場所の判断はそのまま要るため。
17588
+ chorusLead: "Celesta",
17589
+ chordAlt: "Electric Piano 1",
17590
+ sparkle: "Music Box",
17591
+ bassAlt: "Acoustic Bass",
17592
+ harmonyAlt: "Choir Aahs"
17593
+ },
17594
+ acoustic: {
17595
+ displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
17596
+ description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
17597
+ melody: "Acoustic Guitar (steel)",
17598
+ submelody: "Harmonica",
17599
+ bass: "Acoustic Bass",
17600
+ chord: "Acoustic Guitar (nylon)",
17601
+ solo: "Overdriven Guitar",
17602
+ chorusLead: "String Ensemble 1",
17603
+ chordAlt: "Acoustic Grand Piano",
17604
+ sparkle: "Celesta",
17605
+ bassAlt: "Electric Bass (finger)",
17606
+ harmonyAlt: "Choir Aahs"
17607
+ },
17608
+ jazz_night: {
17609
+ displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
17610
+ 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",
17611
+ melody: "Electric Piano 1",
17612
+ submelody: "Flute",
17613
+ bass: "Acoustic Bass",
17614
+ chord: "Electric Guitar (jazz)",
17615
+ solo: "Tenor Sax",
17616
+ chorusLead: "Muted Trumpet",
17617
+ chordAlt: "Vibraphone",
17618
+ sparkle: "Celesta",
17619
+ bassAlt: "Electric Bass (finger)",
17620
+ harmonyAlt: "Choir Aahs"
17621
+ },
17622
+ // --- MODERN & VIBE: エッジの効いた現代的な響き ---
17623
+ synth_pop: {
17624
+ displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
17625
+ description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
17626
+ melody: "Lead 2 (sawtooth)",
17627
+ submelody: "Lead 4 (chiff)",
17628
+ bass: "Synth Bass 2",
17629
+ chord: "Pad 3 (polysynth)",
17630
+ solo: "Distortion Guitar",
17631
+ chorusLead: "Synth Brass 1",
17632
+ chordAlt: "Electric Piano 2",
17633
+ sparkle: "FX 3 (crystal)",
17634
+ bassAlt: "Synth Bass 1",
17635
+ harmonyAlt: "Synth Choir"
17636
+ },
17637
+ cyber_punk: {
17638
+ displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
17639
+ 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",
17640
+ melody: "Lead 8 (bass + lead)",
17641
+ submelody: "Lead 5 (charang)",
17642
+ bass: "Synth Bass 2",
17643
+ chord: "Pad 8 (sweep)",
17644
+ solo: "Distortion Guitar",
17645
+ chorusLead: "Lead 7 (fifths)",
17646
+ chordAlt: "Pad 4 (choir)",
17647
+ sparkle: "FX 3 (crystal)",
17648
+ bassAlt: "Synth Bass 1",
17649
+ harmonyAlt: "Synth Choir"
17650
+ },
17651
+ rock: {
17652
+ displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
17653
+ description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
17654
+ melody: "Distortion Guitar",
17655
+ submelody: "Rock Organ",
17656
+ bass: "Electric Bass (pick)",
17657
+ chord: "Overdriven Guitar",
17658
+ solo: "Distortion Guitar",
17659
+ chorusLead: "Brass Section",
17660
+ chordAlt: "Electric Guitar (clean)",
17661
+ sparkle: "Electric Guitar (muted)",
17662
+ bassAlt: "Electric Bass (finger)",
17663
+ harmonyAlt: "Choir Aahs"
17664
+ },
17665
+ // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
17666
+ orchestra: {
17667
+ displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
17668
+ description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
17669
+ melody: "French Horn",
17670
+ submelody: "Pizzicato Strings",
17671
+ bass: "Cello",
17672
+ chord: "Tremolo Strings",
17673
+ solo: "Violin",
17674
+ chorusLead: "Trumpet",
17675
+ chordAlt: "String Ensemble 1",
17676
+ sparkle: "Orchestral Harp",
17677
+ bassAlt: "Contrabass",
17678
+ harmonyAlt: "Choir Aahs"
17679
+ },
17680
+ japanese_wa: {
17681
+ displayName: "\u548C\u98A8\u30FB\u96C5",
17682
+ description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
17683
+ melody: "Koto",
17684
+ submelody: "Shamisen",
17685
+ bass: "Taiko Drum",
17686
+ chord: "Shakuhachi",
17687
+ solo: "Shakuhachi",
17688
+ // piano プリセットと同じ理由で Glockenspiel を避ける(音源のピッチずれ)。
17689
+ chorusLead: "Celesta",
17690
+ chordAlt: "Kalimba",
17691
+ sparkle: "Music Box",
17692
+ bassAlt: "Acoustic Bass",
17693
+ harmonyAlt: "Choir Aahs"
17694
+ },
17695
+ arabic_exotic: {
17696
+ displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
17697
+ 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",
17698
+ melody: "Sitar",
17699
+ submelody: "Bagpipe",
17700
+ bass: "Fretless Bass",
17701
+ chord: "Kalimba",
17702
+ solo: "Shanai",
17703
+ chorusLead: "Steel Drums",
17704
+ chordAlt: "Orchestral Harp",
17705
+ sparkle: "Tinkle Bell",
17706
+ bassAlt: "Acoustic Bass",
17707
+ harmonyAlt: "Choir Aahs"
17708
+ },
17709
+ // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
17710
+ fantasy_rpg: {
17711
+ displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
17712
+ description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
17713
+ melody: "Ocarina",
17714
+ submelody: "Celesta",
17715
+ bass: "Timpani",
17716
+ chord: "Orchestral Harp",
17717
+ solo: "Pan Flute",
17718
+ chorusLead: "Choir Aahs",
17719
+ chordAlt: "String Ensemble 2",
17720
+ sparkle: "Tinkle Bell",
17721
+ bassAlt: "Contrabass",
17722
+ harmonyAlt: "Choir Aahs"
17723
+ },
17724
+ ambient_cloud: {
17725
+ displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
17726
+ description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
17727
+ melody: "Lead 6 (voice)",
17728
+ submelody: "Music Box",
17729
+ bass: "Synth Bass 1",
17730
+ chord: "Pad 7 (halo)",
17731
+ solo: "Lead 3 (calliope)",
17732
+ chorusLead: "Synth Choir",
17733
+ chordAlt: "Pad 5 (bowed)",
17734
+ sparkle: "FX 3 (crystal)",
17735
+ bassAlt: "Synth Bass 2",
17736
+ harmonyAlt: "Synth Choir"
17737
+ },
17738
+ retro_game: {
17739
+ displayName: "8-bit \u30EC\u30C8\u30ED",
17740
+ 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",
17741
+ melody: "Lead 1 (square)",
17742
+ submelody: "Lead 2 (sawtooth)",
17743
+ bass: "Synth Bass 1",
17744
+ chord: "Clavinet",
17745
+ solo: "Lead 8 (bass + lead)",
17746
+ chorusLead: "Lead 4 (chiff)",
17747
+ chordAlt: "Lead 5 (charang)",
17748
+ sparkle: "Xylophone",
17749
+ bassAlt: "Synth Bass 2",
17750
+ harmonyAlt: "Lead 6 (voice)"
17751
+ }
17752
+ };
17753
+ var GM_INSTRUMENT_RANGE = {
17754
+ // 鍵盤・音板
17755
+ "Acoustic Grand Piano": [21, 108],
17756
+ "Electric Piano 1": [28, 103],
17757
+ Clavinet: [36, 96],
17758
+ Celesta: [60, 108],
17759
+ Glockenspiel: [79, 108],
17760
+ "Music Box": [72, 108],
17761
+ Vibraphone: [53, 89],
17762
+ Kalimba: [60, 84],
17763
+ "Orchestral Harp": [23, 104],
17764
+ // 弦・撥弦
17765
+ "Acoustic Guitar (steel)": [40, 83],
17766
+ "Acoustic Guitar (nylon)": [40, 83],
17767
+ "Electric Guitar (clean)": [40, 86],
17768
+ "Electric Guitar (jazz)": [40, 86],
17769
+ "Overdriven Guitar": [40, 88],
17770
+ "Distortion Guitar": [40, 88],
17771
+ Violin: [55, 103],
17772
+ Cello: [36, 76],
17773
+ "String Ensemble 1": [28, 100],
17774
+ "Tremolo Strings": [28, 100],
17775
+ "Pizzicato Strings": [28, 96],
17776
+ Sitar: [48, 79],
17777
+ Shamisen: [48, 84],
17778
+ Koto: [41, 77],
17779
+ // ベース
17780
+ "Acoustic Bass": [28, 60],
17781
+ "Electric Bass (finger)": [28, 67],
17782
+ "Electric Bass (pick)": [28, 67],
17783
+ "Fretless Bass": [28, 67],
17784
+ "Synth Bass 1": [24, 72],
17785
+ "Synth Bass 2": [24, 72],
17786
+ // 管
17787
+ Flute: [60, 96],
17788
+ "Pan Flute": [60, 91],
17789
+ Shakuhachi: [62, 86],
17790
+ Ocarina: [60, 84],
17791
+ Harmonica: [60, 96],
17792
+ Bagpipe: [62, 86],
17793
+ Shanai: [60, 86],
17794
+ "Tenor Sax": [44, 75],
17795
+ Trumpet: [55, 82],
17796
+ "Muted Trumpet": [55, 82],
17797
+ "French Horn": [41, 77],
17798
+ "Brass Section": [41, 84],
17799
+ // 声・打・オルガン
17800
+ "Choir Aahs": [43, 84],
17801
+ "Synth Choir": [43, 84],
17802
+ "Steel Drums": [55, 86],
17803
+ "Taiko Drum": [30, 60],
17804
+ Timpani: [36, 57],
17805
+ "Rock Organ": [36, 96],
17806
+ // シンセ(実物が無いので一般的な使用域)
17807
+ "Synth Brass 1": [36, 96],
17808
+ "Lead 1 (square)": [36, 96],
17809
+ "Lead 2 (sawtooth)": [36, 96],
17810
+ "Lead 3 (calliope)": [48, 96],
17811
+ "Lead 4 (chiff)": [48, 96],
17812
+ "Lead 5 (charang)": [40, 96],
17813
+ "Lead 6 (voice)": [43, 91],
17814
+ "Lead 7 (fifths)": [36, 84],
17815
+ "Lead 8 (bass + lead)": [28, 91],
17816
+ "Pad 2 (warm)": [24, 96],
17817
+ "Pad 3 (polysynth)": [24, 96],
17818
+ "Pad 7 (halo)": [24, 96],
17819
+ "Pad 8 (sweep)": [24, 96]
17820
+ };
17821
+ var GM_BRIGHT_CEILING = {
17822
+ Glockenspiel: 72,
17823
+ "Tinkle Bell": 72,
17824
+ "Music Box": 79,
17825
+ Celesta: 84,
17826
+ Kalimba: 79,
17827
+ "Steel Drums": 84,
17828
+ "FX 3 (crystal)": 79
17829
+ };
17830
+ var OCTAVE_FIT_TOLERANCE = 3;
17831
+ var fitInstrumentOctave = (semitoneRange, instrument, wanted) => {
17832
+ const range = GM_INSTRUMENT_RANGE[instrument];
17833
+ if (!range || !semitoneRange) return wanted;
17834
+ const hi = Math.min(range[1], GM_BRIGHT_CEILING[instrument] ?? range[1]);
17835
+ let octave = wanted;
17836
+ while (octave > wanted - 2) {
17837
+ if (semitoneRange[1] + octave * 12 <= hi + OCTAVE_FIT_TOLERANCE) break;
17838
+ octave--;
17839
+ }
17840
+ return octave;
17841
+ };
17842
+
17843
+ // src/advanced-layers.ts
17844
+ var buildAdvancedLayers = (song, config) => {
17845
+ const { edo, stepsPerBar, preset } = config;
17846
+ const semitoneRange = (notes) => {
17847
+ if (notes.length === 0) return null;
17848
+ let lo = Number.POSITIVE_INFINITY;
17849
+ let hi = Number.NEGATIVE_INFINITY;
17850
+ for (const n of notes) {
17851
+ const semi = n.pitchUnits / UNITS_PER_SEMITONE;
17852
+ if (semi < lo) lo = semi;
17853
+ if (semi > hi) hi = semi;
17854
+ }
17855
+ return [Math.round(lo), Math.round(hi)];
17856
+ };
17857
+ const fit = (notes, slot, wanted) => fitInstrumentOctave(semitoneRange(notes), preset[slot], wanted);
17858
+ const kindAtBar = (bar) => song.sections.find(
17859
+ (sec) => bar >= sec.startBar && bar < sec.startBar + sec.bars
17860
+ )?.kind ?? null;
17861
+ const onlyIn = (notes, kinds) => {
17862
+ if (!kinds) return notes;
17863
+ const want = new Set(kinds);
17864
+ return notes.filter((n) => {
17865
+ const kind = kindAtBar(Math.floor(n.startStep / stepsPerBar));
17866
+ return kind !== null && want.has(kind);
17867
+ });
17868
+ };
17869
+ const chordNotes = (pattern, velocityShift = 0) => buildChordPlacements({
17870
+ edo,
17871
+ chordStr: song.chordProgression,
17872
+ patternType: pattern,
17873
+ rootShift: song.rootShift,
17874
+ bpm: song.bpm,
17875
+ stepsPerBar
17876
+ }).map((p) => ({
17877
+ startStep: p.startStep,
17878
+ pitchUnits: p.pitchUnits,
17879
+ durationSteps: p.durationSteps,
17880
+ velocity: Math.max(30, p.velocity + velocityShift)
17881
+ }));
17882
+ const plan = song.arrange;
17883
+ const kindsPresent = [...new Set(song.sections.map((sec) => sec.kind))];
17884
+ const LOUD = ["chorus", "drop_chorus"];
17885
+ const loudKinds = kindsPresent.filter((k) => LOUD.includes(k));
17886
+ const quietKinds = kindsPresent.filter((k) => !LOUD.includes(k));
17887
+ const splittable = loudKinds.length > 0 && quietKinds.length > 0;
17888
+ const leadNotes = plan.lead ? onlyIn(song.melody, plan.lead.sections) : [];
17889
+ const leadOctave = fit(leadNotes, "chorusLead", plan.lead?.octave ?? 0);
17890
+ const leadLayer = {
17891
+ index: 1,
17892
+ notes: leadNotes,
17893
+ octave: leadOctave,
17894
+ // ユニゾンで重ねるときは、オクターブ上より前に出やすいので少し引く。
17895
+ volume: leadOctave === 0 ? 54 : 62,
17896
+ slot: "chorusLead"
17897
+ };
17898
+ const bassNotes = plan.bassLayer ? onlyIn(song.bass, plan.bassLayer.sections) : [];
17899
+ const bassOctave = fit(bassNotes, "bass", plan.bassLayer?.octave ?? 0);
17900
+ const wantBassLayer = plan.bassLayer !== null && bassOctave !== 0;
17901
+ const bassLayer = wantBassLayer ? {
17902
+ index: 5,
17903
+ notes: bassNotes,
17904
+ octave: bassOctave,
17905
+ volume: 58,
17906
+ slot: "bass"
17907
+ } : {
17908
+ index: 5,
17909
+ notes: splittable ? onlyIn(song.bass, loudKinds) : [],
17910
+ octave: 0,
17911
+ volume: 92,
17912
+ slot: "bassAlt"
17913
+ };
17914
+ const padNotes = onlyIn(song.pad, plan.padSections);
17915
+ const layers = [
17916
+ { index: 0, notes: song.melody, octave: 0, volume: 104, slot: "melody" },
17917
+ leadLayer,
17918
+ // t2 ハモリ。**セクションで分割しない。**
17919
+ // 一度 t12 と loud/quiet で分けてみたが、ハモリは音の87%が盛り上がる側にあり、
17920
+ // 71%の曲は静かな側に1音も無い——分割すると t2 が空になって中身が t12 へ移る。
17921
+ // 「トラック番号と役割の対応は固定」(ピアノロールのどこに何があるかが曲ごとに
17922
+ // 動くとユーザーが編集できない)という原則にも反するので、ここは丸ごと持つ。
17923
+ { index: 2, notes: song.harmony, octave: 0, volume: 82 },
17924
+ // t3 サブメロ。t14 が間奏ソロを持たないとき、そちらへ盛り上がる側を渡して
17925
+ // こちらは静かな側だけ持つ。対旋律・合いの手がセクションで音色替えする。
17926
+ {
17927
+ index: 3,
17928
+ notes: song.solo.length === 0 && splittable ? onlyIn(song.submelody, quietKinds) : song.submelody,
17929
+ octave: 0,
17930
+ volume: 86,
17931
+ slot: "submelody"
17932
+ },
17933
+ // t4。t5 が `bassAlt` で盛り上がる側を持つときは、こちらは静かな側だけ持つ
17934
+ // (分割であって間引きではない——2本合わせて元のベースが漏れなく鳴る)。
17935
+ {
17936
+ index: 4,
17937
+ notes: !wantBassLayer && splittable ? onlyIn(song.bass, quietKinds) : song.bass,
17938
+ octave: 0,
17939
+ volume: 92,
17940
+ slot: "bass"
17941
+ },
17942
+ bassLayer,
17943
+ // **パッドは伴奏用の楽器で、伴奏より1.5オクターブ高いところを鳴らす。**
17944
+ // そのまま置くとナイロンギターで10半音、カリンバで9半音、尺八で7半音ぶん
17945
+ // 音域を突き抜ける。楽器に合わせて下げる。
17946
+ {
17947
+ index: 6,
17948
+ notes: padNotes,
17949
+ octave: fit(padNotes, "chord", 0),
17950
+ volume: 64,
17951
+ slot: "chord"
17952
+ }
17953
+ ];
17954
+ const backingVolumes = [62, 54, 50];
17955
+ for (let i2 = 0; i2 < 3; i2++) {
17956
+ const layer = plan.backing[i2];
17957
+ layers.push({
17958
+ index: 7 + i2,
17959
+ notes: layer ? onlyIn(chordNotes(layer.pattern), layer.sections) : [],
17960
+ octave: layer?.octave ?? 0,
17961
+ volume: backingVolumes[i2],
17962
+ slot: i2 % 2 === 0 ? "chordAlt" : "chord"
17963
+ });
17964
+ }
17965
+ const sparkleNotes = plan.sparkle ? onlyIn(chordNotes(plan.sparkle.pattern, -14), plan.sparkle.sections) : [];
17966
+ layers.push(
17967
+ {
17968
+ index: 10,
17969
+ notes: sparkleNotes,
17970
+ // 装飾は伴奏と別の音色にする(以前はここも `chord` の使い回しだった)。
17971
+ octave: fit(sparkleNotes, "sparkle", plan.sparkle?.octave ?? 0),
17972
+ volume: 56,
17973
+ slot: "sparkle"
17974
+ },
17975
+ // 掛け合い(デュエット)の相手。**歌入り作曲のときだけ**中身が入る。
17976
+ { index: 11, notes: [], octave: 0, volume: 104, slot: "melody" },
17977
+ // 2声目のハモリ(主旋律を上下から挟む3声)と、主旋律のオクターブ下の重ね。
17978
+ // どちらも曲ごとに出るかどうかが決まる(`song.vocal`)。
17979
+ // t12。3声目のハモリは曲ごとに出るかが決まり、実測28%。
17980
+ //
17981
+ // 出ない曲では**パッド(t6)が鳴っていないセクション**を、別の音色
17982
+ // (`harmonyAlt` =声もの・柔らかい持続音)で受け持つ。パッドはセクション種別の
17983
+ // 39%しか覆っておらず補集合は常に存在するので、ここは**いま何も鳴っていない
17984
+ // 場所に持続音を足す**ことになる——重ねでも写しでもなく、純粋な追加。
17985
+ song.harmony2.length > 0 ? { index: 12, notes: song.harmony2, octave: 0, volume: 74 } : (() => {
17986
+ const rest = plan.padSections ? kindsPresent.filter((k) => !plan.padSections?.includes(k)) : [];
17987
+ const notes = rest.length > 0 ? onlyIn(song.pad, rest) : [];
17988
+ return {
17989
+ index: 12,
17990
+ notes,
17991
+ octave: fit(notes, "harmonyAlt", 0),
17992
+ volume: 60,
17993
+ slot: "harmonyAlt"
17994
+ };
17995
+ })(),
17996
+ // t13。既定は**主旋律のオクターブ下の重ね**で、歌入り作曲ではここに歌詞が付く
17997
+ // (オクターブ下でハモる歌手。単なる写しではない)。
17998
+ //
17999
+ // ただしこの層は曲ごとに出るかどうかが決まり、実測で**18%しか鳴らない**。
18000
+ // 残り82%はトラックが1本まるごと遊ぶ。オクターブ等価の写しに常設の価値は
18001
+ // 無いが、**空けておく価値はもっと無い**ので、層が無い曲では
18002
+ // 「t1(サビ重ね)が担当しないセクションの主旋律を、別の楽器でなぞる」層に回す。
18003
+ // t1 は CHORUS/LOUD 側を持つので、こちらは静かな側を持つ——結果として
18004
+ // **セクションの境目で主旋律に付く音色が入れ替わる**。1トラック1楽器の制約下で
18005
+ // 「パートごとに楽器が変わる」を作れるのは、この書き分けだけ。
18006
+ song.octave.length > 0 ? {
18007
+ index: 13,
18008
+ notes: song.octave,
18009
+ octave: -1,
18010
+ volume: 56,
18011
+ slot: "melody"
18012
+ } : (() => {
18013
+ const leadKinds = new Set(plan.lead?.sections ?? []);
18014
+ const quiet = song.sections.filter((sec) => sec.spec.melody && !leadKinds.has(sec.kind)).map((sec) => sec.kind);
18015
+ const notes = quiet.length > 0 ? onlyIn(song.melody, quiet) : [];
18016
+ const slot = ["solo", "chorusLead", "submelody", "chordAlt"].find(
18017
+ (k) => preset[k] !== preset.melody && preset[k] !== preset.chord
18018
+ ) ?? "submelody";
18019
+ return {
18020
+ index: 13,
18021
+ notes,
18022
+ // ユニゾンで置く。**音色だけを変えるのが狙い**なので、
18023
+ // オクターブを動かすと「オクターブ写し」に戻ってしまう。
18024
+ octave: fit(notes, slot, 0),
18025
+ volume: 58,
18026
+ slot
18027
+ };
18028
+ })(),
18029
+ // **間奏のソロ。** 音が入るのは間奏の小節だけなので、この1本だけを
18030
+ // 別の楽器にしても他のセクションの鳴りは変わらない。歌の音域をそのまま
18031
+ // 渡すと管楽器が上へ抜ける(テナーサックスで10半音)ので、ここも合わせる。
18032
+ // t14。**間奏のソロ。** ただし間奏は既定の構成(`DEFAULT_SECTIONS`)に無く、
18033
+ // テンプレートも `jpop_standard` しか含まないので、実測で**0%**——設計上の
18034
+ // 出番が既定では一度も来ないトラックだった。
18035
+ // 間奏が無い曲では、サブメロの盛り上がる側を `solo` の音色で受け持つ
18036
+ // (t3 は静かな側。分割なので音数は増えない)。
18037
+ song.solo.length > 0 ? {
18038
+ index: 14,
18039
+ notes: song.solo,
18040
+ octave: fit(song.solo, "solo", 0),
18041
+ volume: 100,
18042
+ slot: "solo"
18043
+ } : (() => {
18044
+ const notes = splittable ? onlyIn(song.submelody, loudKinds) : [];
18045
+ return {
18046
+ index: 14,
18047
+ notes,
18048
+ octave: fit(notes, "solo", 0),
18049
+ volume: 86,
18050
+ slot: "solo"
18051
+ };
18052
+ })()
18053
+ );
18054
+ return layers;
18055
+ };
18056
+
16625
18057
  // src/delay.ts
16626
18058
  var DELAY_DIVISIONS = [
16627
18059
  { value: "4", label: "4\u5206", beats: 1 },
@@ -17042,6 +18474,34 @@ var buildUI = (target, options) => {
17042
18474
  <span class="dtm-grow"></span>
17043
18475
  <span class="dtm-hint" data-dtm="compose-key-hint"></span>
17044
18476
  </div>
18477
+ <div class="dtm-row" data-dtm="compose-scale-row">
18478
+ <span class="dtm-label">\u97F3\u968E</span>
18479
+ <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">
18480
+ <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>
18481
+ <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>
18482
+ <optgroup label="\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF085\u97F3\u97F3\u968E\uFF09">
18483
+ <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>
18484
+ <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>
18485
+ <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>
18486
+ <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>
18487
+ <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>
18488
+ </optgroup>
18489
+ <optgroup label="\u30C1\u30E3\u30FC\u30C1\u30E2\u30FC\u30C9\uFF087\u97F3\u97F3\u968E\uFF09">
18490
+ <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>
18491
+ <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>
18492
+ <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>
18493
+ <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>
18494
+ </optgroup>
18495
+ <optgroup label="\u7279\u6B8A\u97F3\u968E\uFF08\u97F3\u7A0B\u96C6\u5408\u3054\u3068\u5165\u308C\u66FF\u308F\u308B\uFF09">
18496
+ <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>
18497
+ <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>
18498
+ <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>
18499
+ <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>
18500
+ </optgroup>
18501
+ </select>
18502
+ <span class="dtm-grow"></span>
18503
+ <span class="dtm-hint" data-dtm="compose-scale-hint"></span>
18504
+ </div>
17045
18505
  </div>
17046
18506
  <div class="dtm-row">
17047
18507
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
@@ -17244,6 +18704,8 @@ var buildUI = (target, options) => {
17244
18704
  composeSectionsLen: sel("compose-sections-len"),
17245
18705
  composeKey: sel("compose-key"),
17246
18706
  composeKeyHint: sel("compose-key-hint"),
18707
+ composeScale: sel("compose-scale"),
18708
+ composeScaleHint: sel("compose-scale-hint"),
17247
18709
  macroComposeVocal: sel("macro-compose-vocal"),
17248
18710
  macroComposeInfo: sel("macro-compose-info"),
17249
18711
  macroClear: sel("macro-clear"),
@@ -17278,115 +18740,12 @@ var buildUI = (target, options) => {
17278
18740
  };
17279
18741
  };
17280
18742
 
17281
- // src/instrument-presets.ts
17282
- var INSTRUMENT_PRESETS = {
17283
- // --- STANDARD: 汎用性と完成度重視 ---
17284
- piano: {
17285
- displayName: "\u30B0\u30E9\u30F3\u30C9\u30D4\u30A2\u30CE",
17286
- 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",
17287
- melody: "Acoustic Grand Piano",
17288
- submelody: "Vibraphone",
17289
- bass: "Electric Bass (finger)",
17290
- chord: "Pad 2 (warm)"
17291
- },
17292
- acoustic: {
17293
- displayName: "\u30A2\u30B3\u30FC\u30B9\u30C6\u30A3\u30C3\u30AF",
17294
- description: "\u751F\u697D\u5668\u306E\u6E29\u304B\u307F\u3092\u91CD\u8996\u3002\u30D5\u30A9\u30FC\u30AF\u3084\u30DD\u30C3\u30D7\u30B9\u306B\u3002",
17295
- melody: "Acoustic Guitar (steel)",
17296
- submelody: "Harmonica",
17297
- bass: "Acoustic Bass",
17298
- chord: "Acoustic Guitar (nylon)"
17299
- },
17300
- jazz_night: {
17301
- displayName: "\u30B8\u30E3\u30BA\u30FB\u30CA\u30A4\u30C8",
17302
- 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",
17303
- melody: "Electric Piano 1",
17304
- submelody: "Flute",
17305
- bass: "Acoustic Bass",
17306
- chord: "Electric Guitar (jazz)"
17307
- },
17308
- // --- MODERN & VIBE: エッジの効いた現代的な響き ---
17309
- synth_pop: {
17310
- displayName: "\u30B7\u30F3\u30BB\u30DD\u30C3\u30D7",
17311
- description: "80s\u301C\u73FE\u4EE3\u307E\u3067\u3002\u629C\u3051\u308B\u30EA\u30FC\u30C9\u3068\u592A\u3044\u30D9\u30FC\u30B9\u306E\u738B\u9053\u3002",
17312
- melody: "Lead 2 (sawtooth)",
17313
- submelody: "Lead 4 (chiff)",
17314
- bass: "Synth Bass 2",
17315
- chord: "Pad 3 (polysynth)"
17316
- },
17317
- cyber_punk: {
17318
- displayName: "\u30B5\u30A4\u30D0\u30FC\u30D1\u30F3\u30AF",
17319
- 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",
17320
- melody: "Lead 8 (bass + lead)",
17321
- submelody: "Lead 5 (charang)",
17322
- bass: "Synth Bass 2",
17323
- chord: "Pad 8 (sweep)"
17324
- },
17325
- rock: {
17326
- displayName: "\u30CF\u30FC\u30C9\u30ED\u30C3\u30AF",
17327
- description: "\u6B6A\u307F\u30AE\u30BF\u30FC\u3068\u91CD\u539A\u306A\u30D9\u30FC\u30B9\u3067\u3001\u30D1\u30EF\u30FC\u3092\u524D\u9762\u306B\u3002",
17328
- melody: "Distortion Guitar",
17329
- submelody: "Rock Organ",
17330
- bass: "Electric Bass (pick)",
17331
- chord: "Overdriven Guitar"
17332
- },
17333
- // --- WORLD & CLASSIC: 特定のジャンル・地域 ---
17334
- orchestra: {
17335
- displayName: "\u30AA\u30FC\u30B1\u30B9\u30C8\u30E9",
17336
- description: "\u58EE\u5927\u306A\u7269\u8A9E\u3092\u4E88\u611F\u3055\u305B\u308B\u3001\u7BA1\u5F26\u697D\u5668\u306E\u91CD\u539A\u306A\u97FF\u304D\u3002",
17337
- melody: "French Horn",
17338
- submelody: "Pizzicato Strings",
17339
- bass: "Cello",
17340
- chord: "Tremolo Strings"
17341
- },
17342
- japanese_wa: {
17343
- displayName: "\u548C\u98A8\u30FB\u96C5",
17344
- description: "\u7434\u3068\u4E09\u5473\u7DDA\u306E\u7E4A\u7D30\u306A\u8ABF\u3079\u306B\u3001\u5C3A\u516B\u306E\u60C5\u7DD2\u3092\u6DFB\u3048\u3066\u3002",
17345
- melody: "Koto",
17346
- submelody: "Shamisen",
17347
- bass: "Taiko Drum",
17348
- chord: "Shakuhachi"
17349
- },
17350
- arabic_exotic: {
17351
- displayName: "\u30A8\u30AD\u30BE\u30C1\u30C3\u30AF",
17352
- 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",
17353
- melody: "Sitar",
17354
- submelody: "Bagpipe",
17355
- bass: "Fretless Bass",
17356
- chord: "Kalimba"
17357
- },
17358
- // --- FANTASY & ATMOSPHERE: 雰囲気と余韻 ---
17359
- fantasy_rpg: {
17360
- displayName: "\u30D5\u30A1\u30F3\u30BF\u30B8\u30FCRPG",
17361
- description: "\u30AA\u30AB\u30EA\u30CA\u3068\u30CF\u30FC\u30D7\u304C\u7D21\u3050\u3001\u5192\u967A\u3068\u9B54\u6CD5\u306E\u4E16\u754C\u89B3\u3002",
17362
- melody: "Ocarina",
17363
- submelody: "Celesta",
17364
- bass: "Timpani",
17365
- chord: "Orchestral Harp"
17366
- },
17367
- ambient_cloud: {
17368
- displayName: "\u30A2\u30F3\u30D3\u30A8\u30F3\u30C8",
17369
- description: "\u8F2A\u90ED\u3092\u307C\u304B\u3057\u305F\u97F3\u8272\u3067\u3001\u6DF1\u3044\u6CA1\u5165\u611F\u3068\u4F59\u97FB\u3092\u6F14\u51FA\u3002",
17370
- melody: "Lead 6 (voice)",
17371
- submelody: "Music Box",
17372
- bass: "Synth Bass 1",
17373
- chord: "Pad 7 (halo)"
17374
- },
17375
- retro_game: {
17376
- displayName: "8-bit \u30EC\u30C8\u30ED",
17377
- 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",
17378
- melody: "Lead 1 (square)",
17379
- submelody: "Lead 2 (sawtooth)",
17380
- bass: "Synth Bass 1",
17381
- chord: "Clavinet"
17382
- }
17383
- };
17384
-
17385
18743
  // src/macro-state.ts
17386
18744
  var MACRO_STORAGE_KEYS = {
17387
18745
  template: "dtm-macro:template",
17388
18746
  sections: "dtm-macro:sections",
17389
18747
  key: "dtm-macro:key",
18748
+ scale: "dtm-macro:scale",
17390
18749
  shift: "dtm-macro:shift",
17391
18750
  transpose: "dtm-macro:transpose"
17392
18751
  };
@@ -19546,6 +20905,7 @@ var COMPOSE_INFO_HTML = `
19546
20905
  <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>
19547
20906
  <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>
19548
20907
  <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>
20908
+ <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>
19549
20909
  </ul>
19550
20910
  <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>
19551
20911
  <h4>\u3067\u304D\u3042\u304C\u308A\u306E\u9078\u3073\u65B9</h4>
@@ -19564,6 +20924,18 @@ var COMPOSE_INFO_HTML = `
19564
20924
  <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>
19565
20925
  </ul>
19566
20926
  <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>
20927
+ <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>
20928
+ <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>
20929
+ <ul>
20930
+ <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>
20931
+ <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>
20932
+ <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>
20933
+ <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>
20934
+ </ul>
20935
+ <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>
20936
+ <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>
20937
+ <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>
20938
+ <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>
19567
20939
  <h4>\u305D\u306E\u307B\u304B</h4>
19568
20940
  <ul>
19569
20941
  <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>
@@ -19830,26 +21202,6 @@ var pickComposeVocal = (exclude) => {
19830
21202
  const list = pool.length > 0 ? pool : COMPOSE_VOCAL_POOL;
19831
21203
  return list[Math.floor(Math.random() * list.length)] ?? "klatt";
19832
21204
  };
19833
- var ADVANCED_COMPOSE_LAYOUT = [
19834
- { index: 0, part: "melody", octave: 0, volume: 104 },
19835
- { index: 1, part: "melody", octave: 1, volume: 62 },
19836
- { index: 2, part: "harmony", octave: 0, volume: 82 },
19837
- { index: 3, part: "submelody", octave: 0, volume: 86 },
19838
- { index: 4, part: "bass", octave: 0, volume: 92 },
19839
- { index: 5, part: "bass", octave: -1, volume: 58 },
19840
- { index: 6, part: "pad", octave: 0, volume: 64 },
19841
- { index: 7, part: "block", octave: 0, volume: 62 },
19842
- { index: 8, part: "arpeggio", octave: 0, volume: 54 },
19843
- { index: 9, part: "offbeat", octave: 0, volume: 50 },
19844
- { index: 10, part: "uwamono", octave: 1, volume: 56 },
19845
- // 掛け合い(デュエット)の相手。**歌入り作曲のときだけ**中身が入る。
19846
- // 作曲だけならメロディは t0 が全部持つので、ここは空のまま。
19847
- { index: 11, part: "duet", octave: 0, volume: 104 },
19848
- // 2声目のハモリ(主旋律を上下から挟む3声)と、主旋律のオクターブ下の重ね。
19849
- // どちらも曲ごとに出るかどうかが決まる(`song.vocal`)。
19850
- { index: 12, part: "harmony2", octave: 0, volume: 74 },
19851
- { index: 13, part: "melody", octave: -1, volume: 56 }
19852
- ];
19853
21205
  var LYRIC_MODEL_CATEGORIES = [
19854
21206
  {
19855
21207
  label: "kusa\u30D7\u30EA\u30BB\u30C3\u30C8",
@@ -20005,7 +21357,7 @@ var mountDAW = (target, options = {}) => {
20005
21357
  showChord,
20006
21358
  showMidiSearch,
20007
21359
  // 「作曲」は simple では役割固定の4トラックへ、advanced では15トラックへ
20008
- // 編曲を展開する({@link ADVANCED_COMPOSE_LAYOUT})。どちらでも出す。
21360
+ // 編曲を展開する({@link buildAdvancedLayers})。どちらでも出す。
20009
21361
  showCompose: true
20010
21362
  });
20011
21363
  refs.masterVolume.value = String(options.masterVolume ?? 50);
@@ -20199,6 +21551,14 @@ var mountDAW = (target, options = {}) => {
20199
21551
  while (customVocalsMap.has(`custom${n}`)) n++;
20200
21552
  return `custom${n}`;
20201
21553
  };
21554
+ const getVocalIconSrc = (lyricModel) => {
21555
+ if (!lyricModel) return void 0;
21556
+ const customDef = customVocalsMap.get(lyricModel);
21557
+ if (customDef !== void 0)
21558
+ return customDef.iconUrl || FALLBACK_VOCAL_ICON;
21559
+ const imgKey = VOICE_IMAGE_KEY[lyricModel.toLowerCase()];
21560
+ return imgKey ? VOICE_IMAGES[imgKey] : void 0;
21561
+ };
20202
21562
  let selectedNotes = [];
20203
21563
  let selectionRect = null;
20204
21564
  let copiedNotes = [];
@@ -20332,6 +21692,7 @@ var mountDAW = (target, options = {}) => {
20332
21692
  vocalTension: t1?.vocalTension ?? 50,
20333
21693
  vocalOctaveUnison: t1?.vocalOctaveUnison ?? "none",
20334
21694
  trackInstrument: t1?.trackInstrument ?? "",
21695
+ composeSlot: null,
20335
21696
  trackCompression: t1?.trackCompression ?? 0,
20336
21697
  trackWidth: t1?.trackWidth ?? 100,
20337
21698
  trackReverbSend: t1?.trackReverbSend ?? 0,
@@ -21383,25 +22744,38 @@ var mountDAW = (target, options = {}) => {
21383
22744
  refs.undoBtn.disabled = !core.canUndo();
21384
22745
  refs.redoBtn.disabled = !core.canRedo();
21385
22746
  };
21386
- const updateTrackPanel = () => {
22747
+ const updateTrackTabs = () => {
21387
22748
  refs.trackTabs.innerHTML = "";
21388
22749
  trackPillEls.clear();
21389
22750
  for (const [i2, t] of trackStates.entries()) {
21390
22751
  const [r, g, b] = t.config.color;
21391
22752
  const isActive = t.config.id === activeTrackId;
21392
22753
  const btn = document.createElement("button");
21393
- btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""}`;
22754
+ const vocalIconSrc = getVocalIconSrc(t.lyricModel);
22755
+ btn.className = `dtm-pill ${isActive ? "dtm-pill--active" : ""} ${vocalIconSrc ? "dtm-pill--vocal" : ""}`;
21394
22756
  btn.style.setProperty("--dtm-pill-color", `rgb(${r},${g},${b})`);
22757
+ if (vocalIconSrc) {
22758
+ btn.style.setProperty(
22759
+ "--dtm-pill-icon",
22760
+ `url(${JSON.stringify(vocalIconSrc)})`
22761
+ );
22762
+ }
21395
22763
  btn.title = `Track ${i2 + 1}: ${t.config.name}`;
21396
22764
  btn.setAttribute(
21397
22765
  "aria-label",
21398
- `Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}`
22766
+ `Track ${i2 + 1}: ${t.config.name}${isActive ? " (\u9078\u629E\u4E2D)" : ""}${vocalIconSrc ? "\uFF08\u30DC\u30FC\u30AB\u30EB\u9078\u629E\u4E2D\uFF09" : ""}`
21399
22767
  );
21400
- btn.textContent = String(i2 + 1);
22768
+ const labelEl = document.createElement("span");
22769
+ labelEl.className = "dtm-pill__label";
22770
+ labelEl.textContent = String(i2 + 1);
22771
+ btn.appendChild(labelEl);
21401
22772
  btn.addEventListener("click", () => switchTrack(t.config.id));
21402
22773
  refs.trackTabs.appendChild(btn);
21403
22774
  trackPillEls.set(t.config.id, btn);
21404
22775
  }
22776
+ };
22777
+ const updateTrackPanel = () => {
22778
+ updateTrackTabs();
21405
22779
  const active = getActive();
21406
22780
  const activeIndex = trackStates.findIndex(
21407
22781
  (t) => t.config.id === activeTrackId
@@ -21718,6 +23092,7 @@ var mountDAW = (target, options = {}) => {
21718
23092
  syncInstDisabled();
21719
23093
  instSel.addEventListener("change", () => {
21720
23094
  active.trackInstrument = instSel.value;
23095
+ active.composeSlot = null;
21721
23096
  const trackIndex = trackStates.indexOf(active);
21722
23097
  options.onTrackInstrumentChange?.(trackIndex, active.trackInstrument);
21723
23098
  persistTrack1(active);
@@ -22121,6 +23496,7 @@ var mountDAW = (target, options = {}) => {
22121
23496
  redrawAll();
22122
23497
  fireLyricsChange(active);
22123
23498
  reloadVoicesForModel(active.lyricModel);
23499
+ updateTrackTabs();
22124
23500
  });
22125
23501
  lyricCustomGuide.addEventListener("click", () => {
22126
23502
  showModal("\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0(.koe)\u306E\u4F7F\u3044\u65B9", KOE_INFO_HTML);
@@ -22654,6 +24030,7 @@ var mountDAW = (target, options = {}) => {
22654
24030
  }
22655
24031
  trackStates.forEach((t, i2) => {
22656
24032
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
24033
+ t.composeSlot = null;
22657
24034
  const name = normalizeInstrumentName(meta.trackInstruments?.[i2] ?? "");
22658
24035
  if (t.trackInstrument !== name) {
22659
24036
  t.trackInstrument = name;
@@ -22905,7 +24282,7 @@ var mountDAW = (target, options = {}) => {
22905
24282
  const p = programOfInstrumentName(t.trackInstrument);
22906
24283
  if (p !== null) program = p;
22907
24284
  } else if (!t.lyricModel) {
22908
- const role = DECLARED_ROLE[t.config.id] ?? "melody";
24285
+ const role = t.composeSlot ?? DECLARED_ROLE[t.config.id] ?? "melody";
22909
24286
  const instName = autoPreset[role] ?? autoPreset.melody;
22910
24287
  const p = programOfInstrumentName(instName);
22911
24288
  if (p !== null) program = p;
@@ -23274,7 +24651,7 @@ var mountDAW = (target, options = {}) => {
23274
24651
  if (!stats) continue;
23275
24652
  const role = DECLARED_ROLE[t.config.id] ?? classifyTrackRole(stats, t.config.id === topSingleVoiceId);
23276
24653
  roleByTrackId.set(t.config.id, role);
23277
- const instName = autoPreset[role];
24654
+ const instName = autoPreset[t.composeSlot ?? role] ?? autoPreset[role];
23278
24655
  t.trackInstrument = instName;
23279
24656
  const trackIndex = trackStates.indexOf(t);
23280
24657
  options.onTrackInstrumentChange?.(trackIndex, instName);
@@ -23425,6 +24802,15 @@ var mountDAW = (target, options = {}) => {
23425
24802
  refs.composeKey.value = savedKey;
23426
24803
  }
23427
24804
  }
24805
+ const savedScale = readMacroSetting("scale");
24806
+ if (savedScale && refs.composeScale) {
24807
+ const hasOption = Array.from(refs.composeScale.options).some(
24808
+ (opt) => opt.value === savedScale
24809
+ );
24810
+ if (hasOption) {
24811
+ refs.composeScale.value = savedScale;
24812
+ }
24813
+ }
23428
24814
  const savedShift = readMacroSetting("shift");
23429
24815
  if (savedShift && refs.shiftSelect) {
23430
24816
  const hasOption = Array.from(refs.shiftSelect.options).some(
@@ -23487,6 +24873,20 @@ var mountDAW = (target, options = {}) => {
23487
24873
  });
23488
24874
  updateComposeKeyHint();
23489
24875
  }
24876
+ const updateComposeScaleHint = () => {
24877
+ if (!refs.composeScale || !refs.composeScaleHint) return;
24878
+ const desc = getComposeScaleDescription(refs.composeScale.value);
24879
+ refs.composeScaleHint.textContent = desc;
24880
+ refs.composeScaleHint.title = desc;
24881
+ };
24882
+ const composeScale = refs.composeScale;
24883
+ if (composeScale) {
24884
+ composeScale.addEventListener("change", () => {
24885
+ writeMacroSetting("scale", composeScale.value);
24886
+ updateComposeScaleHint();
24887
+ });
24888
+ updateComposeScaleHint();
24889
+ }
23490
24890
  if (refs.shiftSelect) {
23491
24891
  refs.shiftSelect.addEventListener("change", () => {
23492
24892
  writeMacroSetting("shift", refs.shiftSelect.value);
@@ -23507,6 +24907,7 @@ var mountDAW = (target, options = {}) => {
23507
24907
  sections: selectedComposeSections(),
23508
24908
  template: tmpl,
23509
24909
  baseKey: refs.composeKey?.value ?? "any",
24910
+ scale: refs.composeScale?.value ?? "auto",
23510
24911
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
23511
24912
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
23512
24913
  recent: recentComposeFingerprints
@@ -23540,45 +24941,39 @@ var mountDAW = (target, options = {}) => {
23540
24941
  }
23541
24942
  track.core.endBatch();
23542
24943
  };
24944
+ const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
24945
+ if (shouldAutoInstrument && song.instrument) {
24946
+ currentInstrument = song.instrument;
24947
+ autoComposeInstrument = song.instrument;
24948
+ options.onInstrumentChange?.(song.instrument);
24949
+ }
23543
24950
  if (isAdvanced) {
23544
- for (const layer of ADVANCED_COMPOSE_LAYOUT) {
24951
+ const layers = buildAdvancedLayers(song, {
24952
+ edo: renderConfig.edo,
24953
+ stepsPerBar: renderConfig.stepsPerBar,
24954
+ preset: INSTRUMENT_PRESETS[currentInstrument] ?? INSTRUMENT_PRESETS.piano
24955
+ });
24956
+ for (const layer of layers) {
23545
24957
  const track = trackStates[layer.index];
23546
24958
  if (!track) continue;
23547
- 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({
23548
- edo: renderConfig.edo,
23549
- chordStr: song.chordProgression,
23550
- patternType: "arpeggio",
23551
- rootShift: song.rootShift,
23552
- bpm: song.bpm,
23553
- stepsPerBar: renderConfig.stepsPerBar
23554
- }).map((p) => ({
23555
- startStep: p.startStep,
23556
- pitchUnits: p.pitchUnits,
23557
- durationSteps: p.durationSteps,
23558
- velocity: Math.max(30, p.velocity - 14)
23559
- })) : buildChordPlacements({
23560
- edo: renderConfig.edo,
23561
- chordStr: song.chordProgression,
23562
- patternType: layer.part,
23563
- rootShift: song.rootShift,
23564
- bpm: song.bpm,
23565
- stepsPerBar: renderConfig.stepsPerBar
23566
- }).map((p) => ({
23567
- startStep: p.startStep,
23568
- pitchUnits: p.pitchUnits,
23569
- durationSteps: p.durationSteps,
23570
- velocity: p.velocity
23571
- }));
23572
- writeTrackAt(layer.index, notes);
24959
+ writeTrackAt(layer.index, layer.notes);
23573
24960
  track.trackOctave = layer.octave;
23574
24961
  track.volume = layer.volume;
23575
24962
  track.core.setVolume(layer.volume);
24963
+ track.composeSlot = layer.notes.length > 0 ? layer.slot ?? null : null;
24964
+ if (layer.notes.length === 0 && track.trackInstrument) {
24965
+ track.trackInstrument = "";
24966
+ options.onTrackInstrumentChange?.(layer.index, "");
24967
+ }
23576
24968
  }
23577
24969
  for (let i2 = 0; i2 < trackStates.length; i2++) {
23578
- if (ADVANCED_COMPOSE_LAYOUT.some((l) => l.index === i2)) continue;
24970
+ if (layers.some((l) => l.index === i2)) continue;
23579
24971
  writeTrackAt(i2, []);
24972
+ const t = trackStates[i2];
24973
+ if (t) t.composeSlot = null;
23580
24974
  }
23581
24975
  } else {
24976
+ for (const t of trackStates) t.composeSlot = null;
23582
24977
  writeTrack("melody", song.melody);
23583
24978
  writeTrack("submelody", song.submelody);
23584
24979
  writeTrack("bass", song.bass);
@@ -23595,12 +24990,6 @@ var mountDAW = (target, options = {}) => {
23595
24990
  refs.drumSelect.value = song.drum;
23596
24991
  options.onDrumChange?.(song.drum);
23597
24992
  applyDrumPatternFont(song.drum);
23598
- const shouldAutoInstrument = !currentInstrument || currentInstrument === "auto" || currentInstrument === autoComposeInstrument;
23599
- if (shouldAutoInstrument && song.instrument) {
23600
- currentInstrument = song.instrument;
23601
- autoComposeInstrument = song.instrument;
23602
- options.onInstrumentChange?.(song.instrument);
23603
- }
23604
24993
  if (withVocal) {
23605
24994
  autoComposeVocalTracks.clear();
23606
24995
  const melodyTrack = isAdvanced ? trackStates[0] : trackStates.find((t) => t.config.id === "melody");
@@ -24837,6 +26226,7 @@ var mountDAW = (target, options = {}) => {
24837
26226
  if (!t) return;
24838
26227
  const name = normalizeInstrumentName(instrumentName);
24839
26228
  t.trackInstrument = name;
26229
+ t.composeSlot = null;
24840
26230
  if (t.config.id === activeTrackId) updateTrackPanel();
24841
26231
  },
24842
26232
  noteToCanvas: (step, pitch) => {
@@ -24960,8 +26350,9 @@ var playSingingMML = async (mml, options = {}) => {
24960
26350
  });
24961
26351
  const ownsCtx = !options.audioContext;
24962
26352
  const ctx = options.audioContext ?? new AudioContext();
24963
- const destination = options.destination ?? ctx.destination;
26353
+ const rawDestination = options.destination ?? ctx.destination;
24964
26354
  const useSynth = options.synth ?? !options.onPlayNote;
26355
+ const destination = createSafetyLimiter(ctx, rawDestination);
24965
26356
  const synth = useSynth ? createSynth(ctx, destination) : null;
24966
26357
  const pauseWhenHidden = options.pauseWhenHidden ?? ownsCtx;
24967
26358
  let playing = false;
@@ -25967,21 +27358,29 @@ var createDtmStudio = async (options = {}) => {
25967
27358
  };
25968
27359
  applyGlueCompression(options.masterCompression ?? 0);
25969
27360
  const setMasterCompression = (amount) => applyGlueCompression(amount);
25970
- const safetyLimiter = audioCtx.createDynamicsCompressor();
25971
- safetyLimiter.threshold.value = -1;
25972
- safetyLimiter.knee.value = 0;
25973
- safetyLimiter.ratio.value = 20;
25974
- safetyLimiter.attack.value = 1e-3;
25975
- safetyLimiter.release.value = 0.1;
25976
- glueMakeup.connect(safetyLimiter);
25977
27361
  const fadeGain = audioCtx.createGain();
25978
27362
  fadeGain.gain.value = 1;
25979
- safetyLimiter.connect(fadeGain);
25980
27363
  fadeGain.connect(options.destination ?? audioCtx.destination);
27364
+ const safetyLimiter = createSafetyLimiter(audioCtx, fadeGain);
27365
+ glueMakeup.connect(safetyLimiter);
27366
+ const FADE_RESTORE_SEC = 0.02;
27367
+ const rampFadeGainToUnity = (from, at) => {
27368
+ fadeGain.gain.setValueAtTime(from, at);
27369
+ fadeGain.gain.linearRampToValueAtTime(1, at + FADE_RESTORE_SEC);
27370
+ };
27371
+ const restoreFadeGainIfMuted = () => {
27372
+ const current = fadeGain.gain.value;
27373
+ if (current >= 1) return;
27374
+ const now = audioCtx.currentTime;
27375
+ fadeGain.gain.cancelScheduledValues(now);
27376
+ rampFadeGainToUnity(current, now);
27377
+ };
25981
27378
  const scheduleFade = (params) => {
25982
- fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
27379
+ const now = audioCtx.currentTime;
27380
+ const current = fadeGain.gain.value;
27381
+ fadeGain.gain.cancelScheduledValues(now);
25983
27382
  if (!params) {
25984
- fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
27383
+ rampFadeGainToUnity(current, now);
25985
27384
  return;
25986
27385
  }
25987
27386
  const { fadeInStartAt, fadeInEndAt, fadeOutStartAt, fadeOutEndAt } = params;
@@ -25989,13 +27388,12 @@ var createDtmStudio = async (options = {}) => {
25989
27388
  fadeGain.gain.setValueAtTime(0, fadeInStartAt);
25990
27389
  fadeGain.gain.linearRampToValueAtTime(1, fadeInEndAt);
25991
27390
  } else {
25992
- fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
27391
+ rampFadeGainToUnity(current, now);
25993
27392
  }
25994
27393
  if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
25995
27394
  fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
25996
27395
  fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
25997
- fadeGain.gain.setValueAtTime(0, fadeOutEndAt + 2);
25998
- fadeGain.gain.setValueAtTime(1, fadeOutEndAt + 2);
27396
+ rampFadeGainToUnity(0, fadeOutEndAt + 2);
25999
27397
  }
26000
27398
  };
26001
27399
  const clipMeter = createClipMeter(audioCtx, glueMakeup);
@@ -26055,8 +27453,7 @@ var createDtmStudio = async (options = {}) => {
26055
27453
  };
26056
27454
  };
26057
27455
  const resumeAudio = () => {
26058
- fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
26059
- fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
27456
+ restoreFadeGainIfMuted();
26060
27457
  if (audioCtx.state === "closed") return Promise.resolve();
26061
27458
  return audioCtx.resume();
26062
27459
  };
@@ -26267,12 +27664,6 @@ var createDtmStudio = async (options = {}) => {
26267
27664
  await listReady;
26268
27665
  nameToKey = await buildNameToKeyMapping();
26269
27666
  await Promise.all([drumReady, loadPreset(defaultPreset)]);
26270
- const restoreFadeGainIfMuted = () => {
26271
- if (fadeGain.gain.value < 1) {
26272
- fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
26273
- fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
26274
- }
26275
- };
26276
27667
  const playDrum = (e) => {
26277
27668
  if (!sfDrum.font) return;
26278
27669
  if (e.when === 0) restoreFadeGainIfMuted();
@@ -27023,7 +28414,7 @@ var createDtmStudio = async (options = {}) => {
27023
28414
  };
27024
28415
  const setMasterVolume = (volume) => {
27025
28416
  const g = Math.max(0, Math.min(100, volume)) / 100;
27026
- masterGain.gain.setValueAtTime(g, audioCtx.currentTime);
28417
+ masterGain.gain.setTargetAtTime(g, audioCtx.currentTime, 0.02);
27027
28418
  };
27028
28419
  let recordDestNode = null;
27029
28420
  const createMediaStreamDestination = () => {
@@ -27106,10 +28497,13 @@ var createDtmStudio = async (options = {}) => {
27106
28497
  0 && (module.exports = {
27107
28498
  A4_HZ,
27108
28499
  A4_UNITS,
28500
+ BLUES_SCALE,
27109
28501
  BREATH_MARK,
27110
28502
  CENTS_PER_UNIT,
27111
28503
  COMPOSE_KEYS,
27112
28504
  COMPOSE_MOOD_GROUPS,
28505
+ COMPOSE_SCALES,
28506
+ COMPOSE_SCALE_IDS,
27113
28507
  CORPUS_BANDS,
27114
28508
  CORPUS_SIZE,
27115
28509
  DAW_CSS,
@@ -27129,7 +28523,11 @@ var createDtmStudio = async (options = {}) => {
27129
28523
  FADE_IN_MARK,
27130
28524
  FADE_OUT_MARK,
27131
28525
  GLOBAL_STORAGE_KEYS,
28526
+ GM_BRIGHT_CEILING,
27132
28527
  GM_INSTRUMENT_NAMES,
28528
+ GM_INSTRUMENT_RANGE,
28529
+ HARMONIC_MINOR_SCALE,
28530
+ HUNGARIAN_SCALE,
27133
28531
  INSTRUMENT_PRESETS,
27134
28532
  KEY_COUNT,
27135
28533
  KOE_BASE_URL,
@@ -27139,6 +28537,7 @@ var createDtmStudio = async (options = {}) => {
27139
28537
  LinkedList,
27140
28538
  MACRO_STORAGE_KEYS,
27141
28539
  MAJOR_KEY_IDS,
28540
+ MAJOR_SCALE,
27142
28541
  MAX_VOCAL_VOLUME,
27143
28542
  MICRO_STEP,
27144
28543
  MINOR_KEY_IDS,
@@ -27207,10 +28606,12 @@ var createDtmStudio = async (options = {}) => {
27207
28606
  featureVector,
27208
28607
  fifthToStep,
27209
28608
  fifthToUnits,
28609
+ fitInstrumentOctave,
27210
28610
  formatMmlMeta,
27211
28611
  freqFromPitch,
27212
28612
  generateRandomPattern,
27213
28613
  getComposeKeyDescription,
28614
+ getComposeScaleDescription,
27214
28615
  getDrumPatternKeys,
27215
28616
  getMidiBPM,
27216
28617
  icon,
@@ -27246,9 +28647,13 @@ var createDtmStudio = async (options = {}) => {
27246
28647
  readGlobalSetting,
27247
28648
  readMacroSections,
27248
28649
  readMacroSetting,
28650
+ resolveCenter,
27249
28651
  resolveComposeKey,
28652
+ resolveComposeScale,
27250
28653
  resolveDrumPattern,
27251
28654
  resolveLoopPoint,
28655
+ scaleDegrees,
28656
+ scaleSize,
27252
28657
  semitonesToUnits,
27253
28658
  shiftNotes,
27254
28659
  showLoadingOverlay,