@onjmin/dtm 0.1.85 → 0.1.86

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
@@ -2316,10 +2316,11 @@ var parseLyrics = (mml) => {
2316
2316
  let octave = 0;
2317
2317
  let vibrato = false;
2318
2318
  let reverb = 0;
2319
+ let delay = 0;
2319
2320
  let gender = 50;
2320
2321
  let breathiness = 50;
2321
2322
  const modelMatch = rest.match(
2322
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrgh]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2323
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghe]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2323
2324
  );
2324
2325
  let model = "";
2325
2326
  const metaTokens = [];
@@ -2388,6 +2389,13 @@ var parseLyrics = (mml) => {
2388
2389
  rest = rest.substring(hMatch[0].length).trim();
2389
2390
  continue;
2390
2391
  }
2392
+ const eMatch = rest.match(/^e(\d+)/i);
2393
+ if (eMatch) {
2394
+ delay = clamp(Number.parseInt(eMatch[1], 10), 0, 100);
2395
+ metaTokens.push(eMatch[0]);
2396
+ rest = rest.substring(eMatch[0].length).trim();
2397
+ continue;
2398
+ }
2391
2399
  break;
2392
2400
  }
2393
2401
  const lyricLines = [rest];
@@ -2404,6 +2412,7 @@ var parseLyrics = (mml) => {
2404
2412
  octave,
2405
2413
  vibrato,
2406
2414
  reverb,
2415
+ delay,
2407
2416
  gender,
2408
2417
  breathiness,
2409
2418
  syllables,
@@ -2491,7 +2500,7 @@ var FORMANTS = {
2491
2500
  N: [250, 1e3]
2492
2501
  };
2493
2502
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
2494
- var createKlattVoice = (ctx, destination, reverbBus) => {
2503
+ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
2495
2504
  const active = /* @__PURE__ */ new Set();
2496
2505
  const voice = (syllable, e) => {
2497
2506
  const t0 = ctx.currentTime + e.when;
@@ -2510,11 +2519,17 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2510
2519
  panner.connect(dest);
2511
2520
  out = panner;
2512
2521
  }
2513
- let sendGain = null;
2522
+ let reverbSendGain = null;
2514
2523
  if (reverbBus && e.reverbSend && e.reverbSend > 0 && panner) {
2515
- sendGain = ctx.createGain();
2516
- sendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2517
- panner.connect(sendGain).connect(reverbBus);
2524
+ reverbSendGain = ctx.createGain();
2525
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2526
+ panner.connect(reverbSendGain).connect(reverbBus);
2527
+ }
2528
+ let delaySendGain = null;
2529
+ if (delayBus && e.delaySend && e.delaySend > 0 && panner) {
2530
+ delaySendGain = ctx.createGain();
2531
+ delaySendGain.gain.value = Math.max(0, Math.min(1, e.delaySend));
2532
+ panner.connect(delaySendGain).connect(delayBus);
2518
2533
  }
2519
2534
  const osc = ctx.createOscillator();
2520
2535
  osc.type = "sawtooth";
@@ -2571,7 +2586,8 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2571
2586
  active.delete(osc);
2572
2587
  osc.disconnect();
2573
2588
  panner?.disconnect();
2574
- sendGain?.disconnect();
2589
+ reverbSendGain?.disconnect();
2590
+ delaySendGain?.disconnect();
2575
2591
  };
2576
2592
  };
2577
2593
  voice.stopAll = () => {
@@ -2887,7 +2903,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2887
2903
  return p;
2888
2904
  };
2889
2905
  const LEADCAP_S = 0.09;
2890
- const schedule = (r, t0, peak, pan, reverbSend = 0, destOverride) => {
2906
+ const schedule = (r, t0, peak, pan, reverbSend = 0, delaySend = 0, destOverride) => {
2891
2907
  const dest = destOverride ?? destination;
2892
2908
  let out = dest;
2893
2909
  let panner = null;
@@ -2897,11 +2913,17 @@ var createKoeVoice = async (ctx, destination, options) => {
2897
2913
  panner.connect(dest);
2898
2914
  out = panner;
2899
2915
  }
2900
- let sendGain = null;
2916
+ let reverbSendGain = null;
2901
2917
  if (options.reverbBus && reverbSend > 0 && panner) {
2902
- sendGain = ctx.createGain();
2903
- sendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2904
- panner.connect(sendGain).connect(options.reverbBus);
2918
+ reverbSendGain = ctx.createGain();
2919
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2920
+ panner.connect(reverbSendGain).connect(options.reverbBus);
2921
+ }
2922
+ let delaySendGain = null;
2923
+ if (options.delayBus && delaySend > 0 && panner) {
2924
+ delaySendGain = ctx.createGain();
2925
+ delaySendGain.gain.value = Math.max(0, Math.min(1, delaySend));
2926
+ panner.connect(delaySendGain).connect(options.delayBus);
2905
2927
  }
2906
2928
  const src = ctx.createBufferSource();
2907
2929
  src.buffer = r.audio;
@@ -2928,7 +2950,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2928
2950
  src.disconnect();
2929
2951
  env.disconnect();
2930
2952
  panner?.disconnect();
2931
- sendGain?.disconnect();
2953
+ reverbSendGain?.disconnect();
2954
+ delaySendGain?.disconnect();
2932
2955
  };
2933
2956
  };
2934
2957
  const model = (syllable, e) => {
@@ -2947,7 +2970,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2947
2970
  const pan = e.pan ?? 0;
2948
2971
  const durationMs = Math.max(60, e.duration * 1e3);
2949
2972
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2950
- if (r) schedule(r, t0, peak, pan, e.reverbSend, e.destination);
2973
+ if (r)
2974
+ schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
2951
2975
  });
2952
2976
  };
2953
2977
  model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato, expr) => {
@@ -2965,9 +2989,9 @@ var createKoeVoice = async (ctx, destination, options) => {
2965
2989
  const r = await renderInto(alias, pitch, dMs, vib, expr);
2966
2990
  return r ? keyOf(alias, pitch, dMs, vib, expr) : null;
2967
2991
  };
2968
- model.scheduleCached = (key, t0, peak, pan, reverbSend, dest) => {
2992
+ model.scheduleCached = (key, t0, peak, pan, reverbSend, delaySend, dest) => {
2969
2993
  const r = renderCache.get(key);
2970
- if (r) schedule(r, t0, peak, pan, reverbSend, dest);
2994
+ if (r) schedule(r, t0, peak, pan, reverbSend, delaySend, dest);
2971
2995
  };
2972
2996
  model.stopAll = () => {
2973
2997
  for (const src of active) {
@@ -2997,8 +3021,12 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2997
3021
  let streamSession = 0;
2998
3022
  const masterGain = ctx.createGain();
2999
3023
  masterGain.connect(destination);
3024
+ let masterVolumeScalar = 1;
3000
3025
  const loaded = /* @__PURE__ */ new Map([
3001
- [FALLBACK_MODEL, createKlattVoice(ctx, masterGain, options.reverbBus)]
3026
+ [
3027
+ FALLBACK_MODEL,
3028
+ createKlattVoice(ctx, masterGain, options.reverbBus, options.delayBus)
3029
+ ]
3002
3030
  ]);
3003
3031
  const loading = /* @__PURE__ */ new Map();
3004
3032
  const load2 = (model) => {
@@ -3019,7 +3047,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3019
3047
  worldlineScriptUrl: options.worldlineScriptUrl,
3020
3048
  lightweight: options.lightweight,
3021
3049
  voiceWorkerUrl: options.voiceWorkerUrl,
3022
- reverbBus: options.reverbBus
3050
+ reverbBus: options.reverbBus,
3051
+ delayBus: options.delayBus
3023
3052
  })
3024
3053
  ))().then((v) => {
3025
3054
  loaded.set(m, v);
@@ -3138,13 +3167,16 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3138
3167
  if (key) {
3139
3168
  const delay = ctx.currentTime - t0;
3140
3169
  if (delay < 0.05) {
3170
+ const dest = options.getTrackDestination?.(track.id ?? "");
3171
+ const effPeak = dest ? peak * masterVolumeScalar : peak;
3141
3172
  scheduleCached(
3142
3173
  key,
3143
3174
  t0,
3144
- peak,
3175
+ effPeak,
3145
3176
  track.pan,
3146
3177
  track.reverbSend,
3147
- options.getTrackDestination?.(track.id ?? "")
3178
+ track.delaySend,
3179
+ dest
3148
3180
  );
3149
3181
  opts?.onScheduled?.(track, note, t0);
3150
3182
  } else {
@@ -3157,16 +3189,19 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3157
3189
  })();
3158
3190
  } else {
3159
3191
  const when = t0 - ctx.currentTime;
3192
+ const dest = options.getTrackDestination?.(track.id ?? "");
3193
+ const effPeak = dest ? peak * masterVolumeScalar : peak;
3160
3194
  model(note.syllable, {
3161
3195
  trackId: track.id ?? "",
3162
3196
  pitch: note.pitch,
3163
3197
  velocity: 100,
3164
- volume: peak,
3198
+ volume: effPeak,
3165
3199
  when,
3166
3200
  duration: note.durationSec,
3167
3201
  pan: track.pan,
3168
3202
  reverbSend: track.reverbSend,
3169
- destination: options.getTrackDestination?.(track.id ?? "")
3203
+ delaySend: track.delaySend,
3204
+ destination: dest
3170
3205
  });
3171
3206
  opts?.onScheduled?.(track, note, t0);
3172
3207
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -3191,7 +3226,9 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3191
3226
  for (const v of loaded.values()) v.reset?.();
3192
3227
  };
3193
3228
  const setVolume = (gain) => {
3194
- masterGain.gain.value = Math.max(0, gain);
3229
+ const g = Math.max(0, gain);
3230
+ masterGain.gain.value = g;
3231
+ masterVolumeScalar = g;
3195
3232
  };
3196
3233
  return {
3197
3234
  loadModels,
@@ -3225,10 +3262,13 @@ var PITCH_MAP = {
3225
3262
  b: 11
3226
3263
  };
3227
3264
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3228
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|mode)=([\w-]+)/gi;
3265
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mode)=([\w-]+)/gi;
3229
3266
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3230
3267
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3231
3268
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3269
+ var TRACK_EQLOW_DIRECTIVE = /#t(\d+)eqlo=(-?\d+)/gi;
3270
+ var TRACK_EQMID_DIRECTIVE = /#t(\d+)eqmid=(-?\d+)/gi;
3271
+ var TRACK_EQHIGH_DIRECTIVE = /#t(\d+)eqhi=(-?\d+)/gi;
3232
3272
  var parseMmlMeta = (mml) => {
3233
3273
  const meta = {};
3234
3274
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -3245,6 +3285,17 @@ var parseMmlMeta = (mml) => {
3245
3285
  } else if (key === "reverb") {
3246
3286
  const rv = Number.parseInt(m[2], 10);
3247
3287
  if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3288
+ } else if (key === "reverbdecay") {
3289
+ const rd = Number.parseInt(m[2], 10);
3290
+ if (!Number.isNaN(rd)) meta.reverbDecay = clamp2(rd, 3, 40);
3291
+ } else if (key === "reverbpredelay") {
3292
+ const rp = Number.parseInt(m[2], 10);
3293
+ if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp2(rp, 0, 150);
3294
+ } else if (key === "delay") {
3295
+ const dv = Number.parseInt(m[2], 10);
3296
+ if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3297
+ } else if (key === "delaydiv") {
3298
+ if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3248
3299
  } else if (key === "mode") {
3249
3300
  if (m[2] === "simple" || m[2] === "advanced") {
3250
3301
  meta.mode = m[2];
@@ -3275,9 +3326,33 @@ var parseMmlMeta = (mml) => {
3275
3326
  meta.trackWidth[idx] = val;
3276
3327
  }
3277
3328
  }
3329
+ for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3330
+ const idx = Number.parseInt(m[1], 10);
3331
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3332
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3333
+ meta.trackEqLow ??= {};
3334
+ meta.trackEqLow[idx] = val;
3335
+ }
3336
+ }
3337
+ for (const m of mml.matchAll(TRACK_EQMID_DIRECTIVE)) {
3338
+ const idx = Number.parseInt(m[1], 10);
3339
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3340
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3341
+ meta.trackEqMid ??= {};
3342
+ meta.trackEqMid[idx] = val;
3343
+ }
3344
+ }
3345
+ for (const m of mml.matchAll(TRACK_EQHIGH_DIRECTIVE)) {
3346
+ const idx = Number.parseInt(m[1], 10);
3347
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3348
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3349
+ meta.trackEqHigh ??= {};
3350
+ meta.trackEqHigh[idx] = val;
3351
+ }
3352
+ }
3278
3353
  return meta;
3279
3354
  };
3280
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "");
3355
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "").replace(TRACK_EQLOW_DIRECTIVE, "").replace(TRACK_EQMID_DIRECTIVE, "").replace(TRACK_EQHIGH_DIRECTIVE, "");
3281
3356
  var formatMmlMeta = (meta, space = "") => {
3282
3357
  const parts = [];
3283
3358
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3288,6 +3363,14 @@ var formatMmlMeta = (meta, space = "") => {
3288
3363
  parts.push(`#drumvolume=${meta.drumVolume}`);
3289
3364
  if (meta.reverb !== void 0 && meta.reverb !== 0)
3290
3365
  parts.push(`#reverb=${meta.reverb}`);
3366
+ if (meta.reverbDecay !== void 0 && meta.reverbDecay !== 22)
3367
+ parts.push(`#reverbdecay=${meta.reverbDecay}`);
3368
+ if (meta.reverbPreDelay !== void 0 && meta.reverbPreDelay !== 0)
3369
+ parts.push(`#reverbpredelay=${meta.reverbPreDelay}`);
3370
+ if (meta.delay !== void 0 && meta.delay !== 0)
3371
+ parts.push(`#delay=${meta.delay}`);
3372
+ if (meta.delayDivision && meta.delayDivision !== "8")
3373
+ parts.push(`#delaydiv=${meta.delayDivision}`);
3291
3374
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3292
3375
  if (meta.trackInstruments) {
3293
3376
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
@@ -3304,6 +3387,21 @@ var formatMmlMeta = (meta, space = "") => {
3304
3387
  if (val !== 100) parts.push(`#t${idx}width=${val}`);
3305
3388
  }
3306
3389
  }
3390
+ if (meta.trackEqLow) {
3391
+ for (const [idx, val] of Object.entries(meta.trackEqLow)) {
3392
+ if (val !== 0) parts.push(`#t${idx}eqlo=${val}`);
3393
+ }
3394
+ }
3395
+ if (meta.trackEqMid) {
3396
+ for (const [idx, val] of Object.entries(meta.trackEqMid)) {
3397
+ if (val !== 0) parts.push(`#t${idx}eqmid=${val}`);
3398
+ }
3399
+ }
3400
+ if (meta.trackEqHigh) {
3401
+ for (const [idx, val] of Object.entries(meta.trackEqHigh)) {
3402
+ if (val !== 0) parts.push(`#t${idx}eqhi=${val}`);
3403
+ }
3404
+ }
3307
3405
  return parts.join(space);
3308
3406
  };
3309
3407
  var parseMML = (mml, options = {}) => {
@@ -9139,6 +9237,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9139
9237
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
9140
9238
  vibrato: lt.vibrato,
9141
9239
  reverbSend: (lt.reverb ?? 0) / 100,
9240
+ delaySend: (lt.delay ?? 0) / 100,
9142
9241
  gender: (lt.gender ?? 50) / 100,
9143
9242
  breathiness: (lt.breathiness ?? 50) / 100,
9144
9243
  notes
@@ -10818,6 +10917,71 @@ var mountChordPlayer = (target, chords, options = {}) => {
10818
10917
  };
10819
10918
  };
10820
10919
 
10920
+ // src/delay.ts
10921
+ var DELAY_DIVISIONS = [
10922
+ { value: "4", label: "4\u5206", beats: 1 },
10923
+ { value: "8", label: "8\u5206", beats: 0.5 },
10924
+ { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
10925
+ { value: "16", label: "16\u5206", beats: 0.25 }
10926
+ ];
10927
+ var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
10928
+ var divisionToSeconds = (division, bpm) => {
10929
+ const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
10930
+ const safeBpm = bpm > 0 ? bpm : 120;
10931
+ return 60 / safeBpm * beats;
10932
+ };
10933
+ var DELAY_MAX_WET = 0.45;
10934
+ var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
10935
+ var FEEDBACK_GAIN = 0.3;
10936
+ var MAX_DELAY_SEC = 2;
10937
+ var createDelayBus = (ctx, destination, options = {}) => {
10938
+ const input = ctx.createGain();
10939
+ const delayNode = ctx.createDelay(MAX_DELAY_SEC);
10940
+ const feedback = ctx.createGain();
10941
+ feedback.gain.value = FEEDBACK_GAIN;
10942
+ const wetGain = ctx.createGain();
10943
+ wetGain.gain.value = delayAmountToGain(options.amount ?? 0);
10944
+ input.connect(delayNode);
10945
+ delayNode.connect(feedback);
10946
+ feedback.connect(delayNode);
10947
+ delayNode.connect(wetGain);
10948
+ wetGain.connect(destination);
10949
+ let bpm = options.bpm ?? 120;
10950
+ let division = options.division ?? "8";
10951
+ const applyTime = () => {
10952
+ delayNode.delayTime.setTargetAtTime(
10953
+ divisionToSeconds(division, bpm),
10954
+ ctx.currentTime,
10955
+ 0.05
10956
+ );
10957
+ };
10958
+ applyTime();
10959
+ return {
10960
+ input,
10961
+ setAmount: (amount) => {
10962
+ wetGain.gain.setTargetAtTime(
10963
+ delayAmountToGain(amount),
10964
+ ctx.currentTime,
10965
+ 0.02
10966
+ );
10967
+ },
10968
+ setDivision: (d) => {
10969
+ division = d;
10970
+ applyTime();
10971
+ },
10972
+ setBpm: (b) => {
10973
+ bpm = b;
10974
+ applyTime();
10975
+ },
10976
+ dispose: () => {
10977
+ input.disconnect();
10978
+ delayNode.disconnect();
10979
+ feedback.disconnect();
10980
+ wetGain.disconnect();
10981
+ }
10982
+ };
10983
+ };
10984
+
10821
10985
  // src/daw-ui.ts
10822
10986
  var q = (root, sel) => root.querySelector(sel);
10823
10987
  var buildUI = (target, options) => {
@@ -10934,6 +11098,25 @@ var buildUI = (target, options) => {
10934
11098
  <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10935
11099
  <button class="dtm-infobtn" data-dtm="reverb-amount-info" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10936
11100
  </div>
11101
+ <div class="dtm-row">
11102
+ <span class="dtm-label">Decay</span>
11103
+ <input type="range" class="dtm-range dtm-grow" data-dtm="reverb-decay" value="22" min="3" max="40" step="1" aria-label="\u30EA\u30D0\u30FC\u30D6\u306EDecay\uFF08\u6B8B\u97FF\u306E\u9577\u3055\u3001\u79D2\uFF09">
11104
+ <span class="dtm-label" data-dtm="reverb-decay-label">2.2s</span>
11105
+ </div>
11106
+ <div class="dtm-row">
11107
+ <span class="dtm-label">Pre Delay</span>
11108
+ <input type="range" class="dtm-range dtm-grow" data-dtm="reverb-predelay" value="0" min="0" max="150" step="5" aria-label="\u30EA\u30D0\u30FC\u30D6\u306EPre Delay\uFF08\u539F\u97F3\u304B\u3089\u6B8B\u97FF\u304C\u7ACB\u3061\u4E0A\u304C\u308B\u307E\u3067\u306E\u9045\u5EF6\u3001ms\uFF09">
11109
+ <span class="dtm-label" data-dtm="reverb-predelay-label">0ms</span>
11110
+ </div>
11111
+ <div class="dtm-row">
11112
+ <span class="dtm-label">\u30C7\u30A3\u30EC\u30A4</span>
11113
+ <input type="range" class="dtm-range dtm-grow" data-dtm="delay-amount" value="0" min="0" max="100" aria-label="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\uFF08\u30C6\u30F3\u30DD\u540C\u671F\u3057\u305F\u30A8\u30B3\u30FC\uFF09">
11114
+ <span class="dtm-label" data-dtm="delay-amount-label">0%</span>
11115
+ <select class="dtm-select" data-dtm="delay-division" aria-label="\u30C7\u30A3\u30EC\u30A4\u306E\u97F3\u4FA1\uFF08\u30C6\u30F3\u30DD\u306B\u540C\u671F\uFF09">
11116
+ ${DELAY_DIVISIONS.map((d) => `<option value="${d.value}">${d.label}</option>`).join("")}
11117
+ </select>
11118
+ <button class="dtm-infobtn" data-dtm="delay-amount-info" title="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11119
+ </div>
10937
11120
  </div>
10938
11121
  </details>
10939
11122
 
@@ -11132,6 +11315,14 @@ var buildUI = (target, options) => {
11132
11315
  reverbAmount: sel("reverb-amount"),
11133
11316
  reverbAmountLabel: sel("reverb-amount-label"),
11134
11317
  reverbAmountInfoBtn: sel("reverb-amount-info"),
11318
+ reverbDecay: sel("reverb-decay"),
11319
+ reverbDecayLabel: sel("reverb-decay-label"),
11320
+ reverbPreDelay: sel("reverb-predelay"),
11321
+ reverbPreDelayLabel: sel("reverb-predelay-label"),
11322
+ delayAmount: sel("delay-amount"),
11323
+ delayAmountLabel: sel("delay-amount-label"),
11324
+ delayAmountInfoBtn: sel("delay-amount-info"),
11325
+ delayDivision: sel("delay-division"),
11135
11326
  autoMasterBtn: sel("auto-master"),
11136
11327
  autoMasterInfoBtn: sel("auto-master-info"),
11137
11328
  trackTabs: sel("track-tabs"),
@@ -12765,6 +12956,38 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
12765
12956
  return chordNotes / notes.length >= threshold;
12766
12957
  };
12767
12958
 
12959
+ // src/reverb.ts
12960
+ var DEFAULT_REVERB_DECAY_SEC = 2.2;
12961
+ var MIN_REVERB_DECAY_SEC = 0.3;
12962
+ var MAX_REVERB_DECAY_SEC = 4;
12963
+ var IMPULSE_DECAY_CURVE = 2.5;
12964
+ var DEFAULT_REVERB_PREDELAY_MS = 0;
12965
+ var MIN_REVERB_PREDELAY_MS = 0;
12966
+ var MAX_REVERB_PREDELAY_MS = 150;
12967
+ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
12968
+ const rate = ctx.sampleRate;
12969
+ const length = Math.max(
12970
+ 1,
12971
+ Math.floor(
12972
+ rate * Math.max(
12973
+ MIN_REVERB_DECAY_SEC,
12974
+ Math.min(MAX_REVERB_DECAY_SEC, decaySec)
12975
+ )
12976
+ )
12977
+ );
12978
+ const impulse = ctx.createBuffer(2, length, rate);
12979
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
12980
+ const data = impulse.getChannelData(ch);
12981
+ for (let i2 = 0; i2 < length; i2++) {
12982
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
12983
+ data[i2] = (Math.random() * 2 - 1) * envelope;
12984
+ }
12985
+ }
12986
+ return impulse;
12987
+ };
12988
+ var REVERB_MAX_WET = 0.6;
12989
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
12990
+
12768
12991
  // src/daw.ts
12769
12992
  var CHORD_INFO_HTML2 = `
12770
12993
  <div class="dtm-modal-body-content">
@@ -12836,14 +13059,46 @@ var LYRIC_REVERB_INFO_HTML = `
12836
13059
  <p>\u6B8B\u97FF\u3067\u97F3\u304C\u6EF2\u307F\u3001\u6B4C\u8A5E\u304C\u805E\u304D\u53D6\u308A\u306B\u304F\u304F\u306A\u308A\u307E\u3059\u3002\u30C9\u30E9\u30A4\u306A\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u8FD1\u3044\u300D\u300C\u524D\u306B\u51FA\u308B\u300D\u5370\u8C61\u3001\u30EA\u30D0\u30FC\u30D6\u305F\u3063\u3077\u308A\u306E\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u5965\u884C\u304D\u304C\u3042\u308B\u300D\u300C\u5E7B\u60F3\u7684\u300D\u306A\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u3002\u8868\u73FE\u3057\u305F\u3044\u8DDD\u96E2\u611F\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
12837
13060
  </div>
12838
13061
  `;
12839
- var MASTER_REVERB_INFO_HTML = `
13062
+ var LYRIC_DELAY_INFO_HTML = `
12840
13063
  <div class="dtm-modal-body-content">
12841
13064
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12842
- <p>\u66F2\u5168\u4F53\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308B\u6B8B\u97FF\uFF08\u90E8\u5C4B\u9CF4\u308A\u30FB\u7A7A\u9593\u306E\u97FF\u304D\uFF09\u3067\u3059\u30020%\u3067\u5B8C\u5168\u306B\u30C9\u30E9\u30A4\uFF08\u97FF\u304D\u306A\u3057\uFF09\u3001\u4E0A\u3052\u308B\u307B\u3069\u5E83\u3044\u7A7A\u9593\u3067\u9CF4\u3063\u3066\u3044\u308B\u3088\u3046\u306A\u5965\u884C\u304D\u304C\u51FA\u307E\u3059\u3002</p>
13065
+ <p>\u3053\u306E\u30DC\u30FC\u30AB\u30EB\u30C8\u30E9\u30C3\u30AF\u304B\u3089\u3001\u66F2\u5168\u4F53\u306B\u639B\u304B\u308B\u300C\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u300D\u3078\u3069\u308C\u3060\u3051\u97F3\u3092\u9001\u308B\u304B\u3092\u6C7A\u3081\u307E\u3059\uFF080\u3067\u9001\u3089\u306A\u3044\u3001100\u3067\u76EE\u4E00\u676F\u9001\u308B\uFF09\u3002</p>
13066
+ <h4>\u30DE\u30B9\u30BF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u300D\u3064\u307E\u307F\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
13067
+ <p>\u30DE\u30B9\u30BF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u300D\u3064\u307E\u307F\u304C0%\u3060\u3068\u3001\u3053\u3053\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u3068\u540C\u3058\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002</p>
13068
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13069
+ <p>\u66F2\u5168\u4F53\u306B\u3046\u3063\u3059\u3089\u639B\u3051\u308B\u30EA\u30D0\u30FC\u30D6\u3068\u9055\u3044\u3001\u30C7\u30A3\u30EC\u30A4\u306F\u30CF\u30C3\u30AD\u30EA\u805E\u3053\u3048\u308B\u7E70\u308A\u8FD4\u3057\u306A\u306E\u3067\u3001A\u30E1\u30ED\u306F0%\u30FB\u30B5\u30D3\u306E\u8A9E\u5C3E\u3060\u3051\u9001\u308A\u91CF\u3092\u4E0A\u3052\u308B\u3001\u3068\u3044\u3063\u305F\u30E1\u30EA\u30CF\u30EA\u306E\u3042\u308B\u4F7F\u3044\u65B9\u304C\u52B9\u679C\u7684\u3067\u3059\u3002\u30D0\u30C3\u30AD\u30F3\u30B0\u5168\u4F53\u306B\u5F37\u304F\u639B\u3051\u308B\u3068\u30EA\u30BA\u30E0\u304C\u6FC1\u308A\u307E\u3059\u3002</p>
13070
+ </div>
13071
+ `;
13072
+ var MASTER_REVERB_INFO_HTML = `
13073
+ <div class="dtm-modal-body-content">
13074
+ <h4>\u30EA\u30D0\u30FC\u30D6\uFF08Mix\uFF09</h4>
13075
+ <p>\u66F2\u5168\u4F53\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308B\u6B8B\u97FF\uFF08\u90E8\u5C4B\u9CF4\u308A\u30FB\u7A7A\u9593\u306E\u97FF\u304D\uFF09\u306E\u91CF\u3067\u3059\u30020%\u3067\u5B8C\u5168\u306B\u30C9\u30E9\u30A4\uFF08\u97FF\u304D\u306A\u3057\uFF09\u3001\u4E0A\u3052\u308B\u307B\u3069\u5E83\u3044\u7A7A\u9593\u3067\u9CF4\u3063\u3066\u3044\u308B\u3088\u3046\u306A\u5965\u884C\u304D\u304C\u51FA\u307E\u3059\u3002\u639B\u3051\u3059\u304E\u308B\u3068\u30DF\u30C3\u30AF\u30B9\u5168\u4F53\u306E\u8F2A\u90ED\u304C\u307C\u3084\u3051\u3001\u9060\u304F\u30FB\u3053\u3082\u3063\u305F\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u300210\u301C30%\u7A0B\u5EA6\u304C\u51FA\u767A\u70B9\u306E\u76EE\u5B89\u3067\u3059\u3002</p>
13076
+ <h4>Decay\uFF08\u6B8B\u97FF\u306E\u9577\u3055\uFF09</h4>
13077
+ <p>\u30EA\u30D0\u30FC\u30D6\u304C\u9CF4\u308A\u7D9A\u3051\u308B\u9577\u3055\u3067\u3059\u3002\u9577\u3044\u307B\u3069\u5E83\u3044\u30DB\u30FC\u30EB\u306E\u3088\u3046\u306A\u7A7A\u9593\u306E\u5370\u8C61\u306B\u306A\u308A\u3001\u77ED\u3044\u3068\u72ED\u3044\u90E8\u5C4B\u306E\u3088\u3046\u306A\u5BC6\u7740\u611F\u306B\u306A\u308A\u307E\u3059\u3002\u76EE\u5B89\u3068\u3057\u3066\u3001\u901F\u3044\u66F2\u3067\u306F0.6\u301C1.4\u79D2\u3001\u9045\u3044\u66F2\u30FB\u30D0\u30E9\u30FC\u30C9\u3067\u306F1.8\u301C4.0\u79D2\u7A0B\u5EA6\u304C\u3088\u304F\u4F7F\u308F\u308C\u307E\u3059\u3002</p>
13078
+ <h4>Pre Delay\uFF08\u539F\u97F3\u3068\u30EA\u30D0\u30FC\u30D6\u306E\u9593\u9694\uFF09</h4>
13079
+ <p>\u539F\u97F3\u304C\u9CF4\u3063\u3066\u304B\u3089\u6B8B\u97FF\u304C\u7ACB\u3061\u4E0A\u304C\u308B\u307E\u3067\u306E\u9045\u5EF6\u3067\u3059\u30020ms\u3060\u3068\u539F\u97F3\u3068\u6B8B\u97FF\u304C\u540C\u6642\u306B\u59CB\u307E\u308A\u97F3\u50CF\u304C\u307C\u3084\u3051\u304C\u3061\u3067\u3059\u304C\u3001\u5C11\u3057\uFF08\u6570\u5341ms\u7A0B\u5EA6\uFF09\u5165\u308C\u308B\u3068\u539F\u97F3\u306E\u8F2A\u90ED\u30FB\u30A2\u30BF\u30C3\u30AF\u611F\u3092\u4FDD\u3063\u305F\u307E\u307E\u5965\u884C\u304D\u3092\u8DB3\u305B\u307E\u3059\u3002\u7279\u306B\u30DC\u30FC\u30AB\u30EB\u3067\u52B9\u679C\u7684\u3067\u3059\u3002</p>
12843
13080
  <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12844
- <p>\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u305D\u308C\u305E\u308C\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u3044\u3046\u500B\u5225\u306E\u3064\u307E\u307F\u304C\u3042\u308A\u3001\u305D\u3061\u3089\u304C0%\u306E\u30C8\u30E9\u30C3\u30AF\u306F\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3001\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002\u300C\u30DE\u30B9\u30BF\uFF1D\u6B8B\u97FF\u306E\u8CEA\u3068\u91CF\u305D\u306E\u3082\u306E\u300D\u300C\u30C8\u30E9\u30C3\u30AF\u9001\u308A\uFF1D\u305D\u306E\u30C8\u30E9\u30C3\u30AF\u3092\u3069\u308C\u3060\u3051\u6DF7\u305C\u308B\u304B\u300D\u3068\u3044\u3046\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u697D\u5668\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u73FE\u72B6\u30BB\u30F3\u30C9\u6A5F\u80FD\u304C\u7121\u3044\u305F\u3081\u3001\u5E38\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308A\u307E\u3059\u3002</p>
12845
- <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12846
- <p>\u30DF\u30C3\u30AF\u30B9\u5168\u4F53\u306E\u8F2A\u90ED\u304C\u307C\u3084\u3051\u3001\u9060\u304F\u30FB\u3053\u3082\u3063\u305F\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u300210\u301C30%\u7A0B\u5EA6\u3092\u51FA\u767A\u70B9\u306B\u3001\u66F2\u306E\u30B8\u30E3\u30F3\u30EB\u3084\u7A7A\u9593\u306E\u5E83\u3055\u306E\u30A4\u30E1\u30FC\u30B8\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3059\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
13081
+ <p>\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u305D\u308C\u305E\u308C\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u3044\u3046\u500B\u5225\u306E\u3064\u307E\u307F\u304C\u3042\u308A\u3001\u305D\u3061\u3089\u304C0%\u306E\u30C8\u30E9\u30C3\u30AF\u306F\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u306EMix\u304C0%\u306A\u3089\u3001\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002\u300C\u30DE\u30B9\u30BF\uFF1D\u6B8B\u97FF\u306E\u8CEA\u30FB\u91CF\u30FB\u30BF\u30A4\u30DF\u30F3\u30B0\u305D\u306E\u3082\u306E\u300D\u300C\u30C8\u30E9\u30C3\u30AF\u9001\u308A\uFF1D\u305D\u306E\u30C8\u30E9\u30C3\u30AF\u3092\u3069\u308C\u3060\u3051\u6DF7\u305C\u308B\u304B\u300D\u3068\u3044\u3046\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u697D\u5668\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u73FE\u72B6\u30BB\u30F3\u30C9\u6A5F\u80FD\u304C\u7121\u3044\u305F\u3081\u3001\u5E38\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308A\u307E\u3059\u3002</p>
13082
+ </div>
13083
+ `;
13084
+ var MASTER_DELAY_INFO_HTML = `
13085
+ <div class="dtm-modal-body-content">
13086
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13087
+ <p>\u96E2\u6563\u3057\u305F\u30A8\u30B3\u30FC\uFF08\u97F3\u306E\u7E70\u308A\u8FD4\u3057\uFF09\u3092\u4ED8\u52A0\u3057\u307E\u3059\u3002\u66F2\u306EBPM\u306B\u81EA\u52D5\u540C\u671F\u3059\u308B\u97F3\u4FA1\uFF088\u5206\u30FB\u4ED8\u70B98\u5206\u306A\u3069\uFF09\u3092\u9078\u3093\u3067\u4F7F\u3046\u306E\u304C\u57FA\u672C\u3067\u3001\u7121\u95A2\u4FC2\u306A\u79D2\u6570\u3067\u9CF4\u3089\u3059\u3068\u30EA\u30BA\u30E0\u304C\u5D29\u308C\u3066\u805E\u3053\u3048\u307E\u3059\u3002</p>
13088
+ <h4>\u30EA\u30D0\u30FC\u30D6\u3068\u306E\u9055\u3044</h4>
13089
+ <p>\u30EA\u30D0\u30FC\u30D6\u306F\u62E1\u6563\u3057\u305F\u6B8B\u97FF\u3067\u7A7A\u9593\u5168\u4F53\u306B\u8584\u304F\u639B\u3051\u308B\u306E\u304C\u57FA\u672C\u3067\u3059\u304C\u3001\u30C7\u30A3\u30EC\u30A4\u306F\u30CF\u30C3\u30AD\u30EA\u805E\u3053\u3048\u308B\u7E70\u308A\u8FD4\u3057\u306A\u306E\u3067\u300C\u3053\u3053\u305E\u300D\u3068\u3044\u3046\u5834\u9762\uFF08\u30EA\u30FC\u30C9\u30DC\u30FC\u30AB\u30EB\u306E\u8A9E\u5C3E\u3001\u30AE\u30BF\u30FC\u30BD\u30ED\u3001\u30B7\u30F3\u30BB\u306E\u30D5\u30EC\u30FC\u30BA\u7B49\uFF09\u306B\u30EF\u30F3\u30DD\u30A4\u30F3\u30C8\u3067\u4F7F\u3046\u306E\u304C\u5B9A\u756A\u3067\u3059\u3002\u30D0\u30C3\u30AD\u30F3\u30B0\u5168\u4F53\u306B\u5F37\u304F\u639B\u3051\u308B\u3068\u30EA\u30BA\u30E0\u304C\u6FC1\u308A\u307E\u3059\u3002</p>
13090
+ <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2</h4>
13091
+ <p>\u30EA\u30D0\u30FC\u30D6\u3068\u540C\u3058\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u500B\u5225\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u304C0%\u306A\u3089\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u4E0A\u3052\u3066\u3082\u7121\u97F3\u3001\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002</p>
13092
+ </div>
13093
+ `;
13094
+ var TRACK_EQ_INFO_HTML = `
13095
+ <div class="dtm-modal-body-content">
13096
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13097
+ <p>\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u5468\u6CE2\u6570\u5E2F\u57DF\u3054\u3068\u306E\u97F3\u91CF\u3092\u8ABF\u6574\u3057\u307E\u3059\u3002\u4F4E\u57DF\u30FB\u4E2D\u57DF\u30FB\u9AD8\u57DF\u306E3\u30D0\u30F3\u30C9\u3001\u305D\u308C\u305E\u308C-12dB\uFF08\u524A\u308B\uFF09\u301C+12dB\uFF08\u6301\u3061\u4E0A\u3052\u308B\uFF09\u30010dB\u304C\u7121\u5909\u5316\u3067\u3059\u3002</p>
13098
+ <h4>\u4F7F\u3044\u3069\u3053\u308D\uFF08\u5E2F\u57DF\u306E\u68F2\u307F\u5206\u3051\uFF09</h4>
13099
+ <p>\u8907\u6570\u306E\u97F3\u304C\u540C\u3058\u5E2F\u57DF\u3067\u9CF4\u3063\u3066\u3044\u308B\u3068\u6FC1\u3063\u3066\u805E\u3053\u3048\u307E\u3059\u3002\u4F8B\u3048\u3070\u3001\u30DC\u30FC\u30AB\u30EB\u3068\u30EA\u30FC\u30C9\u697D\u5668\u304C\u4E2D\u57DF\u3067\u3076\u3064\u304B\u308B\u306A\u3089\u7247\u65B9\u306E\u4E2D\u57DF\u3092\u8EFD\u304F\u524A\u308B\u3001\u30D9\u30FC\u30B9\u3068\u30D0\u30B9\u30C9\u30E9\u30E0\u306E\u4F4E\u57DF\u304C\u3076\u3064\u304B\u308B\u306A\u3089\u7247\u65B9\u3060\u3051\u4F4E\u57DF\u3092\u524A\u308B\u3001\u3068\u3044\u3063\u305F\u300C\u5E2F\u57DF\u306E\u4EA4\u901A\u6574\u7406\u300D\u304CEQ\u306E\u4E3B\u306A\u4ED5\u4E8B\u3067\u3059\u3002</p>
13100
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044\uFF08\u9806\u5E8F\u304C\u91CD\u8981\uFF09</h4>
13101
+ <p>\u3053\u306E\u30C1\u30E3\u30F3\u30CD\u30EB\u30B9\u30C8\u30EA\u30C3\u30D7\u3067\u306F EQ \u2192 \u97F3\u5727\u5F37\u5316\uFF08\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\uFF09 \u2192 \u30B9\u30C6\u30EC\u30AA\u5E45 \u306E\u9806\u3067\u51E6\u7406\u3055\u308C\u307E\u3059\u3002\u3053\u308C\u306F\u4E00\u822C\u7684\u306A\u30DF\u30C3\u30AF\u30B9\u306E\u5B9A\u77F3\u3068\u540C\u3058\u3067\u3059\u3002\u4E0D\u8981\u306A\u5E2F\u57DF\uFF08\u4F4E\u57DF\u306E\u3053\u3082\u308A\u3001\u8033\u969C\u308A\u306A\u9AD8\u57DF\u7B49\uFF09\u3092\u5148\u306BEQ\u3067\u524A\u3063\u3066\u304B\u3089\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u3092\u639B\u3051\u308B\u3068\u3001\u30B3\u30F3\u30D7\u304C\u672C\u5F53\u306B\u5FC5\u8981\u306A\u97F3\u3060\u3051\u306B\u53CD\u5FDC\u3059\u308B\u3088\u3046\u306B\u306A\u308A\u3001\u97F3\u5727\u5F37\u5316\u306E\u52B9\u304D\u304C\u826F\u304F\u306A\u308A\u307E\u3059\u3002\u9806\u5E8F\u3092\u5909\u3048\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093\u3002</p>
12847
13102
  </div>
12848
13103
  `;
12849
13104
  var TRACK_COMPRESSION_INFO_HTML = `
@@ -12869,14 +13124,22 @@ var TRACK_WIDTH_INFO_HTML = `
12869
13124
  var AUTO_MASTER_INFO_HTML = `
12870
13125
  <div class="dtm-modal-body-content">
12871
13126
  <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
12872
- <p>\u5E02\u8CA9\u66F2\u3067\u3088\u304F\u4F7F\u308F\u308C\u308B\u5024\u3092\u76EE\u5B89\u306B\u3001\u4EE5\u4E0B\u3092\u307E\u3068\u3081\u3066\u8A2D\u5B9A\u3059\u308B\u30DC\u30BF\u30F3\u3067\u3059\u3002</p>
13127
+ <p>\u5E02\u8CA9\u66F2\u3067\u3088\u304F\u4F7F\u308F\u308C\u308B\u5024\u3092\u76EE\u5B89\u306B\u3001\u4EE5\u4E0B\u3092\u307E\u3068\u3081\u3066\u8A2D\u5B9A\u3059\u308B\u30DC\u30BF\u30F3\u3067\u3059\u3002\u66F2\u306EBPM\u3092\u898B\u3066\u4E00\u90E8\u306E\u5024\u3092\u81EA\u52D5\u8ABF\u6574\u3057\u307E\u3059\u3002</p>
12873
13128
  <ul>
12874
- <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6: 20%</li>
12875
- <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: 35%</li>
13129
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Mix: 20%</li>
13130
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Decay: <strong>\u73FE\u5728\u306EBPM\u306B\u5FDC\u3058\u3066\u81EA\u52D5\u8A08\u7B97</strong>\uFF08\u901F\u3044\u66F2\u307B\u3069\u77ED\u304F0.9\u79D2\u5BC4\u308A\u3001\u9045\u3044\u66F2\u307B\u3069\u9577\u304F3.0\u79D2\u5BC4\u308A\uFF09</li>
13131
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Pre Delay: 20ms\uFF08\u539F\u97F3\u306E\u8F2A\u90ED\u3092\u4FDD\u3063\u305F\u307E\u307E\u5965\u884C\u304D\u3092\u8DB3\u3059\u5B9A\u756A\u5024\uFF09</li>
13132
+ <li>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: \u697D\u566835%\u30FB\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF\u306F25%\uFF08\u5F37\u304F\u6F70\u3059\u3068\u8868\u60C5\u304C\u5931\u308F\u308C\u308B\u305F\u3081\u63A7\u3048\u3081\u306B\uFF09</li>
12876
13133
  <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45: 115%\uFF08\u308F\u305A\u304B\u306B\u5E83\u3052\u308B\uFF09</li>
12877
- <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON\u3001\u30EA\u30D0\u30FC\u30D6\u9001\u308A25%</li>
13134
+ <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON</li>
12878
13135
  </ul>
12879
- <p style="margin-top:4px;"><small>\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
13136
+ <h4>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB\u306E\u81EA\u52D5\u5224\u5B9A</h4>
13137
+ <p>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF\u304C\u8907\u6570\u3042\u308B\u5834\u5408\uFF08\u30CF\u30E2\u30EA\u30FB\u30B3\u30FC\u30E9\u30B9\u30FB\u639B\u3051\u58F0\u7B49\uFF09\u3001\u767A\u97F3\u6642\u9593\u304C\u6700\u3082\u9577\u3044\u30C8\u30E9\u30C3\u30AF\u3092\u300C\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB\u300D\u3068\u307F\u306A\u3057\u3001\u4ED6\u3068\u306F\u9055\u3046\u6271\u3044\u306B\u3057\u307E\u3059\u3002</p>
13138
+ <ul>
13139
+ <li>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB: \u97F3\u5727\u5F37\u531630%\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A15%\uFF08\u524D\u306B\u51FA\u3059\uFF09</li>
13140
+ <li>\u305D\u308C\u4EE5\u5916\u306E\u30DC\u30FC\u30AB\u30EB: \u97F3\u5727\u5F37\u531625%\u30FB\u30EA\u30D0\u30FC\u30D6\u9001\u308A30%\uFF08\u5965\u3078\u99B4\u67D3\u307E\u305B\u308B\uFF09</li>
13141
+ </ul>
13142
+ <p style="margin-top:4px;"><small>\u300C\u524D\u306B\u51FA\u3057\u305F\u3044\u97F3\u306F\u30EA\u30D0\u30FC\u30D6\u5C11\u306A\u3081\u3001\u5965\u306B\u7F6E\u304D\u305F\u3044\u97F3\u306F\u30EA\u30D0\u30FC\u30D6\u591A\u3081\u300D\u3068\u3044\u3046\u5B9A\u77F3\u306B\u57FA\u3065\u3044\u3066\u3044\u307E\u3059\u3002EQ\u306F\u66F2\u30FB\u30D1\u30FC\u30C8\u3054\u3068\u306B\u4F55\u304C\u6FC1\u3063\u3066\u3044\u308B\u304B\u6B21\u7B2C\u3067\u81EA\u52D5\u5316\u306B\u5411\u304B\u306A\u3044\u305F\u3081\u5BFE\u8C61\u5916\u3067\u3059\u3002\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
12880
13143
  </div>
12881
13144
  `;
12882
13145
  var MIDI_INFO_HTML = `
@@ -13112,7 +13375,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
13112
13375
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
13113
13376
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
13114
13377
  };
13115
- var clamp3 = (v, min, max) => Math.min(Math.max(v, min), max);
13378
+ var clamp4 = (v, min, max) => Math.min(Math.max(v, min), max);
13116
13379
  var normalizeInstrumentName = (name) => {
13117
13380
  if (!name) return "";
13118
13381
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13152,6 +13415,17 @@ var mountDAW = (target, options = {}) => {
13152
13415
  refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
13153
13416
  refs.reverbAmount.value = String(options.reverbAmount ?? 0);
13154
13417
  refs.reverbAmountLabel.textContent = `${options.reverbAmount ?? 0}%`;
13418
+ {
13419
+ const initDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
13420
+ const initPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13421
+ refs.reverbDecay.value = String(Math.round(initDecay * 10));
13422
+ refs.reverbDecayLabel.textContent = `${initDecay.toFixed(1)}s`;
13423
+ refs.reverbPreDelay.value = String(initPreDelay);
13424
+ refs.reverbPreDelayLabel.textContent = `${initPreDelay}ms`;
13425
+ }
13426
+ refs.delayAmount.value = String(options.delayAmount ?? 0);
13427
+ refs.delayAmountLabel.textContent = `${options.delayAmount ?? 0}%`;
13428
+ refs.delayDivision.value = options.delayDivision ?? "8";
13155
13429
  refs.drumVolume.value = String(options.drumVolume ?? 80);
13156
13430
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
13157
13431
  const renderConfig = {
@@ -13168,8 +13442,16 @@ var mountDAW = (target, options = {}) => {
13168
13442
  let masterVolume = options.masterVolume ?? 50;
13169
13443
  options.singingVoices?.setVolume(masterVolume / 100);
13170
13444
  let reverbAmount = options.reverbAmount ?? 0;
13445
+ let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
13446
+ let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13447
+ let delayAmount = options.delayAmount ?? 0;
13448
+ let delayDivision = options.delayDivision ?? "8";
13171
13449
  let unsubscribeClip;
13172
13450
  options.onReverbChange?.(reverbAmount);
13451
+ options.onReverbDecayChange?.(reverbDecay);
13452
+ options.onReverbPreDelayChange?.(reverbPreDelay);
13453
+ options.onDelayChange?.(delayAmount);
13454
+ options.onDelayDivisionChange?.(delayDivision);
13173
13455
  let drumVolume = options.drumVolume ?? 80;
13174
13456
  let currentDrumPattern = refs.drumSelect.value;
13175
13457
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
@@ -13250,6 +13532,7 @@ var mountDAW = (target, options = {}) => {
13250
13532
  vocalOctave: t.vocalOctave,
13251
13533
  vocalVibrato: t.vocalVibrato,
13252
13534
  vocalReverb: t.vocalReverb,
13535
+ vocalDelay: t.vocalDelay,
13253
13536
  vocalGender: t.vocalGender,
13254
13537
  vocalBreathiness: t.vocalBreathiness
13255
13538
  };
@@ -13313,11 +13596,15 @@ var mountDAW = (target, options = {}) => {
13313
13596
  vocalOctave: 0,
13314
13597
  vocalVibrato: false,
13315
13598
  vocalReverb: 0,
13599
+ vocalDelay: 0,
13316
13600
  vocalGender: 50,
13317
13601
  vocalBreathiness: 50,
13318
13602
  trackInstrument: "",
13319
13603
  trackCompression: 0,
13320
- trackWidth: 100
13604
+ trackWidth: 100,
13605
+ trackEqLow: 0,
13606
+ trackEqMid: 0,
13607
+ trackEqHigh: 0
13321
13608
  };
13322
13609
  });
13323
13610
  };
@@ -13338,6 +13625,7 @@ var mountDAW = (target, options = {}) => {
13338
13625
  octave: t.vocalOctave,
13339
13626
  vibrato: t.vocalVibrato,
13340
13627
  reverb: t.vocalReverb,
13628
+ delay: t.vocalDelay,
13341
13629
  gender: t.vocalGender,
13342
13630
  breathiness: t.vocalBreathiness,
13343
13631
  syllables
@@ -13460,7 +13748,7 @@ var mountDAW = (target, options = {}) => {
13460
13748
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13461
13749
  const ratio = currentOffsetX / maxOffsetX;
13462
13750
  refs.hScrollThumb.style.width = `${thumbW}px`;
13463
- refs.hScrollThumb.style.left = `${clamp3(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13751
+ refs.hScrollThumb.style.left = `${clamp4(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13464
13752
  }
13465
13753
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13466
13754
  const sbH = refs.vScroll.clientHeight;
@@ -13530,9 +13818,9 @@ var mountDAW = (target, options = {}) => {
13530
13818
  if (maxOffsetX <= 0) return;
13531
13819
  const rect = refs.hScroll.getBoundingClientRect();
13532
13820
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13533
- const x2 = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13821
+ const x2 = clamp4(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13534
13822
  const ratio = x2 / (rect.width - thumbW);
13535
- currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
13823
+ currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
13536
13824
  setDrawOffset(currentOffsetX, currentOffsetY);
13537
13825
  redrawAll();
13538
13826
  };
@@ -13541,9 +13829,9 @@ var mountDAW = (target, options = {}) => {
13541
13829
  if (maxOffset <= 0) return;
13542
13830
  const rect = refs.vScroll.getBoundingClientRect();
13543
13831
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
13544
- const y = clamp3(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13832
+ const y = clamp4(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13545
13833
  const ratio = y / (rect.height - thumbH);
13546
- currentOffsetY = clamp3(ratio * maxOffset, 0, maxOffset);
13834
+ currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
13547
13835
  setDrawOffset(currentOffsetX, currentOffsetY);
13548
13836
  redrawAll();
13549
13837
  };
@@ -13590,8 +13878,8 @@ var mountDAW = (target, options = {}) => {
13590
13878
  if (dx !== 0 || dy !== 0) {
13591
13879
  const maxOffsetX = getMaxOffsetX();
13592
13880
  const maxOffsetY = getMaxOffsetY();
13593
- currentOffsetX = clamp3(currentOffsetX + dx, 0, maxOffsetX);
13594
- currentOffsetY = clamp3(currentOffsetY + dy, 0, maxOffsetY);
13881
+ currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
13882
+ currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
13595
13883
  setDrawOffset(currentOffsetX, currentOffsetY);
13596
13884
  onPointerMove(lastMoveEvent);
13597
13885
  }
@@ -13981,12 +14269,12 @@ var mountDAW = (target, options = {}) => {
13981
14269
  "wheel",
13982
14270
  (event) => {
13983
14271
  event.preventDefault();
13984
- currentOffsetY = clamp3(
14272
+ currentOffsetY = clamp4(
13985
14273
  currentOffsetY + event.deltaY,
13986
14274
  0,
13987
14275
  getMaxOffsetY()
13988
14276
  );
13989
- currentOffsetX = clamp3(
14277
+ currentOffsetX = clamp4(
13990
14278
  currentOffsetX + event.deltaX,
13991
14279
  0,
13992
14280
  getMaxOffsetX()
@@ -14025,7 +14313,7 @@ var mountDAW = (target, options = {}) => {
14025
14313
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
14026
14314
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
14027
14315
  refs.zoomXLabel.textContent = `${zoomX}%`;
14028
- currentOffsetX = clamp3(
14316
+ currentOffsetX = clamp4(
14029
14317
  centerStep * renderConfig.stepWidth - canvas.width / 2,
14030
14318
  0,
14031
14319
  getMaxOffsetX()
@@ -14038,7 +14326,7 @@ var mountDAW = (target, options = {}) => {
14038
14326
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
14039
14327
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
14040
14328
  refs.zoomYLabel.textContent = `${zoomY}%`;
14041
- currentOffsetY = clamp3(
14329
+ currentOffsetY = clamp4(
14042
14330
  centerKey * renderConfig.keyHeight - canvas.height / 2,
14043
14331
  0,
14044
14332
  getMaxOffsetY()
@@ -14088,7 +14376,7 @@ var mountDAW = (target, options = {}) => {
14088
14376
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
14089
14377
  if (currentPlayStep > threshold) {
14090
14378
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
14091
- currentOffsetX = clamp3(
14379
+ currentOffsetX = clamp4(
14092
14380
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
14093
14381
  0,
14094
14382
  getMaxOffsetX()
@@ -14144,6 +14432,7 @@ var mountDAW = (target, options = {}) => {
14144
14432
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
14145
14433
  vibrato: lt.vibrato,
14146
14434
  reverbSend: (lt.reverb ?? 0) / 100,
14435
+ delaySend: (lt.delay ?? 0) / 100,
14147
14436
  gender: (lt.gender ?? 50) / 100,
14148
14437
  breathiness: (lt.breathiness ?? 50) / 100,
14149
14438
  notes
@@ -14173,7 +14462,7 @@ var mountDAW = (target, options = {}) => {
14173
14462
  }
14174
14463
  if (playbackState !== "paused") {
14175
14464
  const canvas = getGridCanvas();
14176
- currentOffsetX = clamp3(
14465
+ currentOffsetX = clamp4(
14177
14466
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14178
14467
  0,
14179
14468
  getMaxOffsetX()
@@ -14253,7 +14542,23 @@ var mountDAW = (target, options = {}) => {
14253
14542
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14254
14543
  </div>
14255
14544
  <details class="dtm-advanced" data-dtm="track-fx-advanced">
14256
- <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14545
+ <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14546
+ <div class="dtm-row">
14547
+ <span class="dtm-label">EQ\u4F4E\u57DF</span>
14548
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-eq-low" min="-12" max="12" step="1" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306EEQ\u4F4E\u57DF\u30B2\u30A4\u30F3\uFF08dB\uFF09">
14549
+ <span class="dtm-label" data-dtm="track-eq-low-label"></span>
14550
+ <button class="dtm-infobtn" data-dtm="track-eq-info" title="EQ\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14551
+ </div>
14552
+ <div class="dtm-row">
14553
+ <span class="dtm-label">EQ\u4E2D\u57DF</span>
14554
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-eq-mid" min="-12" max="12" step="1" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306EEQ\u4E2D\u57DF\u30B2\u30A4\u30F3\uFF08dB\uFF09">
14555
+ <span class="dtm-label" data-dtm="track-eq-mid-label"></span>
14556
+ </div>
14557
+ <div class="dtm-row">
14558
+ <span class="dtm-label">EQ\u9AD8\u57DF</span>
14559
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-eq-high" min="-12" max="12" step="1" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306EEQ\u9AD8\u57DF\u30B2\u30A4\u30F3\uFF08dB\uFF09">
14560
+ <span class="dtm-label" data-dtm="track-eq-high-label"></span>
14561
+ </div>
14257
14562
  <div class="dtm-row">
14258
14563
  <span class="dtm-label">\u97F3\u5727\u5F37\u5316</span>
14259
14564
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-comp" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u91CF\uFF08\u97F3\u5727\u5F37\u5316\uFF09">
@@ -14283,6 +14588,52 @@ var mountDAW = (target, options = {}) => {
14283
14588
  volInput.title = active.lyricModel ? "\u6B4C\u8A5E\u30E2\u30FC\u30C9\u306E\u3068\u304D\u306F\u30D9\u30ED\u30B7\u30C6\u30A3\u304C\u518D\u751F\u306B\u53CD\u6620\u3055\u308C\u307E\u305B\u3093\uFF08\u58F0\u91CF\u3067\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\uFF09" : "";
14284
14589
  };
14285
14590
  syncVelocityDisabled();
14591
+ const trackEqLowInput = refs.trackBody.querySelector(
14592
+ '[data-dtm="track-eq-low"]'
14593
+ );
14594
+ const trackEqLowLabel = refs.trackBody.querySelector(
14595
+ '[data-dtm="track-eq-low-label"]'
14596
+ );
14597
+ const trackEqMidInput = refs.trackBody.querySelector(
14598
+ '[data-dtm="track-eq-mid"]'
14599
+ );
14600
+ const trackEqMidLabel = refs.trackBody.querySelector(
14601
+ '[data-dtm="track-eq-mid-label"]'
14602
+ );
14603
+ const trackEqHighInput = refs.trackBody.querySelector(
14604
+ '[data-dtm="track-eq-high"]'
14605
+ );
14606
+ const trackEqHighLabel = refs.trackBody.querySelector(
14607
+ '[data-dtm="track-eq-high-label"]'
14608
+ );
14609
+ const trackEqInfo = refs.trackBody.querySelector(
14610
+ '[data-dtm="track-eq-info"]'
14611
+ );
14612
+ const fmtDb = (db) => db > 0 ? `+${db}dB` : `${db}dB`;
14613
+ trackEqLowInput.value = String(active.trackEqLow);
14614
+ trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
14615
+ trackEqMidInput.value = String(active.trackEqMid);
14616
+ trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
14617
+ trackEqHighInput.value = String(active.trackEqHigh);
14618
+ trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
14619
+ trackEqLowInput.addEventListener("input", () => {
14620
+ active.trackEqLow = Number.parseInt(trackEqLowInput.value, 10);
14621
+ trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
14622
+ options.onTrackEqLowChange?.(active.config.id, active.trackEqLow);
14623
+ });
14624
+ trackEqMidInput.addEventListener("input", () => {
14625
+ active.trackEqMid = Number.parseInt(trackEqMidInput.value, 10);
14626
+ trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
14627
+ options.onTrackEqMidChange?.(active.config.id, active.trackEqMid);
14628
+ });
14629
+ trackEqHighInput.addEventListener("input", () => {
14630
+ active.trackEqHigh = Number.parseInt(trackEqHighInput.value, 10);
14631
+ trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
14632
+ options.onTrackEqHighChange?.(active.config.id, active.trackEqHigh);
14633
+ });
14634
+ trackEqInfo.addEventListener("click", () => {
14635
+ showModal("EQ\uFF08\u30A4\u30B3\u30E9\u30A4\u30B6\u30FC\uFF09\u306E\u89E3\u8AAC", TRACK_EQ_INFO_HTML);
14636
+ });
14286
14637
  const trackCompInput = refs.trackBody.querySelector(
14287
14638
  '[data-dtm="track-comp"]'
14288
14639
  );
@@ -14435,6 +14786,12 @@ var mountDAW = (target, options = {}) => {
14435
14786
  <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14436
14787
  <button class="dtm-infobtn" data-dtm="lyric-reverb-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14437
14788
  </div>
14789
+ <div class="dtm-row">
14790
+ <span class="dtm-label">\u30C7\u30A3\u30EC\u30A4\u9001\u308A</span>
14791
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-delay" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304B\u3089\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u3078\u9001\u308B\u91CF\uFF08\u30DE\u30B9\u30BF\u306E\u30C7\u30A3\u30EC\u30A4\u3064\u307E\u307F\u304C0%\u3060\u3068\u7121\u97F3\uFF09">
14792
+ <span class="dtm-label" data-dtm="lyric-delay-label"></span>
14793
+ <button class="dtm-infobtn" data-dtm="lyric-delay-info" title="\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14794
+ </div>
14438
14795
  <div class="dtm-row">
14439
14796
  <span class="dtm-label">\u30B8\u30A7\u30F3\u30C0\u30FC</span>
14440
14797
  <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-gender" min="0" max="100" aria-label="\u30D5\u30A9\u30EB\u30DE\u30F3\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC\u30D5\u30A1\u30AF\u30BF\u30FC\uFF08koe\u97F3\u6E90\u9650\u5B9A\uFF09">
@@ -14499,6 +14856,18 @@ var mountDAW = (target, options = {}) => {
14499
14856
  lyricReverbInfo.addEventListener("click", () => {
14500
14857
  showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_REVERB_INFO_HTML);
14501
14858
  });
14859
+ const lyricDelay = lyricDiv.querySelector(
14860
+ '[data-dtm="lyric-delay"]'
14861
+ );
14862
+ const lyricDelayLabel = lyricDiv.querySelector(
14863
+ '[data-dtm="lyric-delay-label"]'
14864
+ );
14865
+ const lyricDelayInfo = lyricDiv.querySelector(
14866
+ '[data-dtm="lyric-delay-info"]'
14867
+ );
14868
+ lyricDelayInfo.addEventListener("click", () => {
14869
+ showModal("\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_DELAY_INFO_HTML);
14870
+ });
14502
14871
  const lyricGender = lyricDiv.querySelector(
14503
14872
  '[data-dtm="lyric-gender"]'
14504
14873
  );
@@ -14595,6 +14964,8 @@ var mountDAW = (target, options = {}) => {
14595
14964
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14596
14965
  lyricReverb.value = String(active.vocalReverb);
14597
14966
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14967
+ lyricDelay.value = String(active.vocalDelay);
14968
+ lyricDelayLabel.textContent = `${active.vocalDelay}%`;
14598
14969
  lyricGender.value = String(active.vocalGender);
14599
14970
  lyricGenderLabel.textContent = `${active.vocalGender}`;
14600
14971
  lyricBreathiness.value = String(active.vocalBreathiness);
@@ -14749,6 +15120,11 @@ var mountDAW = (target, options = {}) => {
14749
15120
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14750
15121
  fireLyricsChange(active);
14751
15122
  });
15123
+ lyricDelay.addEventListener("input", () => {
15124
+ active.vocalDelay = Number.parseInt(lyricDelay.value, 10);
15125
+ lyricDelayLabel.textContent = `${active.vocalDelay}%`;
15126
+ fireLyricsChange(active);
15127
+ });
14752
15128
  lyricGender.addEventListener("input", () => {
14753
15129
  active.vocalGender = Number.parseInt(lyricGender.value, 10);
14754
15130
  lyricGenderLabel.textContent = `${active.vocalGender}`;
@@ -14844,15 +15220,24 @@ var mountDAW = (target, options = {}) => {
14844
15220
  const trackInstrumentsForMeta = {};
14845
15221
  const trackCompressionForMeta = {};
14846
15222
  const trackWidthForMeta = {};
15223
+ const trackEqLowForMeta = {};
15224
+ const trackEqMidForMeta = {};
15225
+ const trackEqHighForMeta = {};
14847
15226
  trackStates.forEach((t, i2) => {
14848
15227
  if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14849
15228
  if (t.trackCompression !== 0)
14850
15229
  trackCompressionForMeta[i2] = t.trackCompression;
14851
15230
  if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
15231
+ if (t.trackEqLow !== 0) trackEqLowForMeta[i2] = t.trackEqLow;
15232
+ if (t.trackEqMid !== 0) trackEqMidForMeta[i2] = t.trackEqMid;
15233
+ if (t.trackEqHigh !== 0) trackEqHighForMeta[i2] = t.trackEqHigh;
14852
15234
  });
14853
15235
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14854
15236
  const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
14855
15237
  const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
15238
+ const trackEqLowMeta = Object.keys(trackEqLowForMeta).length > 0 ? trackEqLowForMeta : void 0;
15239
+ const trackEqMidMeta = Object.keys(trackEqMidForMeta).length > 0 ? trackEqMidForMeta : void 0;
15240
+ const trackEqHighMeta = Object.keys(trackEqHighForMeta).length > 0 ? trackEqHighForMeta : void 0;
14856
15241
  const metaLineFull = formatMmlMeta(
14857
15242
  {
14858
15243
  instrument: currentInstrument || void 0,
@@ -14860,10 +15245,17 @@ var mountDAW = (target, options = {}) => {
14860
15245
  volume: masterVolume,
14861
15246
  drumVolume,
14862
15247
  reverb: reverbAmount,
15248
+ reverbDecay: Math.round(reverbDecay * 10),
15249
+ reverbPreDelay,
15250
+ delay: delayAmount,
15251
+ delayDivision,
14863
15252
  mode,
14864
15253
  trackInstruments: trackInstMeta,
14865
15254
  trackCompression: trackCompMeta,
14866
- trackWidth: trackWidthMeta
15255
+ trackWidth: trackWidthMeta,
15256
+ trackEqLow: trackEqLowMeta,
15257
+ trackEqMid: trackEqMidMeta,
15258
+ trackEqHigh: trackEqHighMeta
14867
15259
  },
14868
15260
  " "
14869
15261
  );
@@ -14874,10 +15266,17 @@ var mountDAW = (target, options = {}) => {
14874
15266
  volume: masterVolume,
14875
15267
  drumVolume,
14876
15268
  reverb: reverbAmount,
15269
+ reverbDecay: Math.round(reverbDecay * 10),
15270
+ reverbPreDelay,
15271
+ delay: delayAmount,
15272
+ delayDivision,
14877
15273
  mode,
14878
15274
  trackInstruments: trackInstMeta,
14879
15275
  trackCompression: trackCompMeta,
14880
- trackWidth: trackWidthMeta
15276
+ trackWidth: trackWidthMeta,
15277
+ trackEqLow: trackEqLowMeta,
15278
+ trackEqMid: trackEqMidMeta,
15279
+ trackEqHigh: trackEqHighMeta
14881
15280
  },
14882
15281
  ""
14883
15282
  );
@@ -14927,6 +15326,7 @@ var mountDAW = (target, options = {}) => {
14927
15326
  oct: t.vocalOctave,
14928
15327
  vib: t.vocalVibrato,
14929
15328
  rev: t.vocalReverb,
15329
+ del: t.vocalDelay,
14930
15330
  gen: t.vocalGender,
14931
15331
  bre: t.vocalBreathiness
14932
15332
  })).filter(
@@ -14939,6 +15339,7 @@ var mountDAW = (target, options = {}) => {
14939
15339
  x2.oct === 0 ? "" : `o${x2.oct}`,
14940
15340
  x2.vib ? "b1" : "",
14941
15341
  x2.rev === 0 ? "" : `r${x2.rev}`,
15342
+ x2.del === 0 ? "" : `e${x2.del}`,
14942
15343
  x2.gen === 50 ? "" : `g${x2.gen}`,
14943
15344
  x2.bre === 50 ? "" : `h${x2.bre}`
14944
15345
  ].filter((s) => s.length > 0).join(" ");
@@ -15009,7 +15410,7 @@ var mountDAW = (target, options = {}) => {
15009
15410
  const canvas = getGridCanvas();
15010
15411
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
15011
15412
  const logicalY = yIndex * renderConfig.keyHeight;
15012
- currentOffsetY = clamp3(
15413
+ currentOffsetY = clamp4(
15013
15414
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
15014
15415
  0,
15015
15416
  getMaxOffsetY()
@@ -15086,6 +15487,29 @@ var mountDAW = (target, options = {}) => {
15086
15487
  refs.reverbAmountLabel.textContent = `${meta.reverb}%`;
15087
15488
  options.onReverbChange?.(meta.reverb);
15088
15489
  }
15490
+ if (meta.reverbDecay !== void 0) {
15491
+ reverbDecay = meta.reverbDecay / 10;
15492
+ refs.reverbDecay.value = String(meta.reverbDecay);
15493
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
15494
+ options.onReverbDecayChange?.(reverbDecay);
15495
+ }
15496
+ if (meta.reverbPreDelay !== void 0) {
15497
+ reverbPreDelay = meta.reverbPreDelay;
15498
+ refs.reverbPreDelay.value = String(meta.reverbPreDelay);
15499
+ refs.reverbPreDelayLabel.textContent = `${meta.reverbPreDelay}ms`;
15500
+ options.onReverbPreDelayChange?.(reverbPreDelay);
15501
+ }
15502
+ if (meta.delay !== void 0) {
15503
+ delayAmount = meta.delay;
15504
+ refs.delayAmount.value = String(meta.delay);
15505
+ refs.delayAmountLabel.textContent = `${meta.delay}%`;
15506
+ options.onDelayChange?.(meta.delay);
15507
+ }
15508
+ if (meta.delayDivision && ["4", "8", "8d", "16"].includes(meta.delayDivision)) {
15509
+ delayDivision = meta.delayDivision;
15510
+ refs.delayDivision.value = delayDivision;
15511
+ options.onDelayDivisionChange?.(delayDivision);
15512
+ }
15089
15513
  }
15090
15514
  trackStates.forEach((t, i2) => {
15091
15515
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -15107,6 +15531,21 @@ var mountDAW = (target, options = {}) => {
15107
15531
  t.trackWidth = width;
15108
15532
  options.onTrackWidthChange?.(t.config.id, width);
15109
15533
  }
15534
+ const eqLow = meta.trackEqLow?.[i2] ?? 0;
15535
+ if (t.trackEqLow !== eqLow) {
15536
+ t.trackEqLow = eqLow;
15537
+ options.onTrackEqLowChange?.(t.config.id, eqLow);
15538
+ }
15539
+ const eqMid = meta.trackEqMid?.[i2] ?? 0;
15540
+ if (t.trackEqMid !== eqMid) {
15541
+ t.trackEqMid = eqMid;
15542
+ options.onTrackEqMidChange?.(t.config.id, eqMid);
15543
+ }
15544
+ const eqHigh = meta.trackEqHigh?.[i2] ?? 0;
15545
+ if (t.trackEqHigh !== eqHigh) {
15546
+ t.trackEqHigh = eqHigh;
15547
+ options.onTrackEqHighChange?.(t.config.id, eqHigh);
15548
+ }
15110
15549
  });
15111
15550
  trackStates.forEach((t, i2) => {
15112
15551
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -15126,6 +15565,7 @@ var mountDAW = (target, options = {}) => {
15126
15565
  active.vocalOctave = 0;
15127
15566
  active.vocalVibrato = false;
15128
15567
  active.vocalReverb = 0;
15568
+ active.vocalDelay = 0;
15129
15569
  active.vocalGender = 50;
15130
15570
  active.vocalBreathiness = 50;
15131
15571
  } else {
@@ -15138,6 +15578,7 @@ var mountDAW = (target, options = {}) => {
15138
15578
  t.vocalOctave = 0;
15139
15579
  t.vocalVibrato = false;
15140
15580
  t.vocalReverb = 0;
15581
+ t.vocalDelay = 0;
15141
15582
  t.vocalGender = 50;
15142
15583
  t.vocalBreathiness = 50;
15143
15584
  }
@@ -15154,6 +15595,7 @@ var mountDAW = (target, options = {}) => {
15154
15595
  t.vocalOctave = lt.octave ?? 0;
15155
15596
  t.vocalVibrato = lt.vibrato ?? false;
15156
15597
  t.vocalReverb = lt.reverb ?? 0;
15598
+ t.vocalDelay = lt.delay ?? 0;
15157
15599
  t.vocalGender = lt.gender ?? 50;
15158
15600
  t.vocalBreathiness = lt.breathiness ?? 50;
15159
15601
  });
@@ -15236,6 +15678,7 @@ var mountDAW = (target, options = {}) => {
15236
15678
  active.vocalOctave = 0;
15237
15679
  active.vocalVibrato = false;
15238
15680
  active.vocalReverb = 0;
15681
+ active.vocalDelay = 0;
15239
15682
  active.vocalGender = 50;
15240
15683
  active.vocalBreathiness = 50;
15241
15684
  } else {
@@ -15250,6 +15693,7 @@ var mountDAW = (target, options = {}) => {
15250
15693
  t.vocalOctave = 0;
15251
15694
  t.vocalVibrato = false;
15252
15695
  t.vocalReverb = 0;
15696
+ t.vocalDelay = 0;
15253
15697
  t.vocalGender = 50;
15254
15698
  t.vocalBreathiness = 50;
15255
15699
  }
@@ -15300,6 +15744,7 @@ var mountDAW = (target, options = {}) => {
15300
15744
  bpm = value;
15301
15745
  refs.bpmInput.value = String(value);
15302
15746
  for (const t of trackStates) t.core.setTempo(value);
15747
+ options.onBpmChange?.(value);
15303
15748
  };
15304
15749
  let lastUndoTime = 0;
15305
15750
  const undo = () => {
@@ -15475,6 +15920,32 @@ var mountDAW = (target, options = {}) => {
15475
15920
  refs.reverbAmountInfoBtn.addEventListener("click", () => {
15476
15921
  showModal("\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC", MASTER_REVERB_INFO_HTML);
15477
15922
  });
15923
+ refs.reverbDecay.addEventListener("input", () => {
15924
+ const raw = Number.parseInt(refs.reverbDecay.value, 10) || 22;
15925
+ reverbDecay = Math.max(
15926
+ MIN_REVERB_DECAY_SEC,
15927
+ Math.min(MAX_REVERB_DECAY_SEC, raw / 10)
15928
+ );
15929
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
15930
+ options.onReverbDecayChange?.(reverbDecay);
15931
+ });
15932
+ refs.reverbPreDelay.addEventListener("input", () => {
15933
+ reverbPreDelay = Number.parseInt(refs.reverbPreDelay.value, 10) || 0;
15934
+ refs.reverbPreDelayLabel.textContent = `${reverbPreDelay}ms`;
15935
+ options.onReverbPreDelayChange?.(reverbPreDelay);
15936
+ });
15937
+ refs.delayAmount.addEventListener("input", () => {
15938
+ delayAmount = Number.parseInt(refs.delayAmount.value, 10) || 0;
15939
+ refs.delayAmountLabel.textContent = `${delayAmount}%`;
15940
+ options.onDelayChange?.(delayAmount);
15941
+ });
15942
+ refs.delayDivision.addEventListener("change", () => {
15943
+ delayDivision = refs.delayDivision.value;
15944
+ options.onDelayDivisionChange?.(delayDivision);
15945
+ });
15946
+ refs.delayAmountInfoBtn.addEventListener("click", () => {
15947
+ showModal("\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC", MASTER_DELAY_INFO_HTML);
15948
+ });
15478
15949
  refs.autoMasterInfoBtn.addEventListener("click", () => {
15479
15950
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15480
15951
  });
@@ -15483,14 +15954,39 @@ var mountDAW = (target, options = {}) => {
15483
15954
  refs.reverbAmount.value = "20";
15484
15955
  refs.reverbAmountLabel.textContent = "20%";
15485
15956
  options.onReverbChange?.(20);
15957
+ const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
15958
+ reverbDecay = clamp4(
15959
+ 3 - bpmT * (3 - 0.9),
15960
+ MIN_REVERB_DECAY_SEC,
15961
+ MAX_REVERB_DECAY_SEC
15962
+ );
15963
+ refs.reverbDecay.value = String(Math.round(reverbDecay * 10));
15964
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
15965
+ options.onReverbDecayChange?.(reverbDecay);
15966
+ reverbPreDelay = 20;
15967
+ refs.reverbPreDelay.value = "20";
15968
+ refs.reverbPreDelayLabel.textContent = "20ms";
15969
+ options.onReverbPreDelayChange?.(20);
15970
+ let mainVocalId = null;
15971
+ let mainVocalScore = -1;
15972
+ for (const t of trackStates) {
15973
+ if (!t.lyricModel) continue;
15974
+ const score = t.core.getNotes().reduce((sum, n) => sum + n.durationSteps, 0);
15975
+ if (score > mainVocalScore) {
15976
+ mainVocalScore = score;
15977
+ mainVocalId = t.config.id;
15978
+ }
15979
+ }
15486
15980
  for (const t of trackStates) {
15487
- t.trackCompression = 35;
15981
+ const isMainVocal = !!t.lyricModel && t.config.id === mainVocalId;
15982
+ const comp = !t.lyricModel ? 35 : isMainVocal ? 30 : 25;
15983
+ t.trackCompression = comp;
15488
15984
  t.trackWidth = 115;
15489
- options.onTrackCompressionChange?.(t.config.id, 35);
15985
+ options.onTrackCompressionChange?.(t.config.id, comp);
15490
15986
  options.onTrackWidthChange?.(t.config.id, 115);
15491
15987
  if (t.lyricModel) {
15492
15988
  t.vocalVibrato = true;
15493
- t.vocalReverb = 25;
15989
+ t.vocalReverb = isMainVocal ? 15 : 30;
15494
15990
  fireLyricsChange(t);
15495
15991
  }
15496
15992
  }
@@ -16372,7 +16868,7 @@ var mountDAW = (target, options = {}) => {
16372
16868
  currentPlayStep = step;
16373
16869
  playbackState = "paused";
16374
16870
  const canvas = getGridCanvas();
16375
- currentOffsetX = clamp3(
16871
+ currentOffsetX = clamp4(
16376
16872
  step * renderConfig.stepWidth - canvas.width * 0.5,
16377
16873
  0,
16378
16874
  getMaxOffsetX()
@@ -16418,11 +16914,11 @@ var mountDAW = (target, options = {}) => {
16418
16914
  getViewState,
16419
16915
  setViewState: (state) => {
16420
16916
  if (typeof state.zoomX === "number") {
16421
- zoomX = clamp3(state.zoomX, 25, 200);
16917
+ zoomX = clamp4(state.zoomX, 25, 200);
16422
16918
  applyZoomX();
16423
16919
  }
16424
16920
  if (typeof state.zoomY === "number") {
16425
- zoomY = clamp3(state.zoomY, 50, 200);
16921
+ zoomY = clamp4(state.zoomY, 50, 200);
16426
16922
  applyZoomY();
16427
16923
  }
16428
16924
  if (typeof state.decomposeChord === "boolean") {
@@ -16444,28 +16940,52 @@ var mountDAW = (target, options = {}) => {
16444
16940
  forcePauseAt,
16445
16941
  setLoading,
16446
16942
  setMasterVolume: (volume) => {
16447
- masterVolume = clamp3(volume, 0, 100);
16943
+ masterVolume = clamp4(volume, 0, 100);
16448
16944
  refs.masterVolume.value = String(masterVolume);
16449
16945
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
16450
16946
  options.singingVoices?.setVolume(masterVolume / 100);
16451
16947
  },
16452
16948
  setVolume: (volume) => {
16453
- masterVolume = clamp3(volume, 0, 100);
16949
+ masterVolume = clamp4(volume, 0, 100);
16454
16950
  refs.masterVolume.value = String(masterVolume);
16455
16951
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
16456
16952
  options.singingVoices?.setVolume(masterVolume / 100);
16457
16953
  },
16458
16954
  setDrumVolume: (volume) => {
16459
- drumVolume = clamp3(volume, 0, 100);
16955
+ drumVolume = clamp4(volume, 0, 100);
16460
16956
  refs.drumVolume.value = String(drumVolume);
16461
16957
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
16462
16958
  },
16463
16959
  setReverbAmount: (amount) => {
16464
- reverbAmount = clamp3(amount, 0, 100);
16960
+ reverbAmount = clamp4(amount, 0, 100);
16465
16961
  refs.reverbAmount.value = String(reverbAmount);
16466
16962
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
16467
16963
  options.onReverbChange?.(reverbAmount);
16468
16964
  },
16965
+ setReverbDecay: (seconds) => {
16966
+ reverbDecay = Math.max(
16967
+ MIN_REVERB_DECAY_SEC,
16968
+ Math.min(MAX_REVERB_DECAY_SEC, seconds)
16969
+ );
16970
+ refs.reverbDecay.value = String(Math.round(reverbDecay * 10));
16971
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
16972
+ options.onReverbDecayChange?.(reverbDecay);
16973
+ },
16974
+ setReverbPreDelay: (ms) => {
16975
+ reverbPreDelay = Math.max(
16976
+ MIN_REVERB_PREDELAY_MS,
16977
+ Math.min(MAX_REVERB_PREDELAY_MS, ms)
16978
+ );
16979
+ refs.reverbPreDelay.value = String(reverbPreDelay);
16980
+ refs.reverbPreDelayLabel.textContent = `${reverbPreDelay}ms`;
16981
+ options.onReverbPreDelayChange?.(reverbPreDelay);
16982
+ },
16983
+ setDelayAmount: (amount) => {
16984
+ delayAmount = clamp4(amount, 0, 100);
16985
+ refs.delayAmount.value = String(delayAmount);
16986
+ refs.delayAmountLabel.textContent = `${delayAmount}%`;
16987
+ options.onDelayChange?.(delayAmount);
16988
+ },
16469
16989
  applyPatch: (trackId, added, removed) => {
16470
16990
  const track = trackStates.find((t) => t.config.id === trackId);
16471
16991
  if (!track) return;
@@ -16507,6 +17027,7 @@ var mountDAW = (target, options = {}) => {
16507
17027
  t.vocalOctave = data.vocalOctave;
16508
17028
  t.vocalVibrato = data.vocalVibrato ?? false;
16509
17029
  t.vocalReverb = data.vocalReverb ?? 0;
17030
+ t.vocalDelay = data.vocalDelay ?? 0;
16510
17031
  t.vocalGender = data.vocalGender ?? 50;
16511
17032
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
16512
17033
  },
@@ -16627,6 +17148,7 @@ var playSingingMML = async (mml, options = {}) => {
16627
17148
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16628
17149
  vibrato: lt.vibrato,
16629
17150
  reverbSend: (lt.reverb ?? 0) / 100,
17151
+ delaySend: (lt.delay ?? 0) / 100,
16630
17152
  gender: (lt.gender ?? 50) / 100,
16631
17153
  breathiness: (lt.breathiness ?? 50) / 100,
16632
17154
  notes
@@ -17237,9 +17759,9 @@ var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
17237
17759
  URL.revokeObjectURL(url);
17238
17760
 
17239
17761
  // src/channel-strip.ts
17240
- var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17762
+ var clamp5 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17241
17763
  var compressionParams = (amount) => {
17242
- const t = clamp4(amount, 0, 100) / 100;
17764
+ const t = clamp5(amount, 0, 100) / 100;
17243
17765
  return {
17244
17766
  threshold: 0 + (-24 - 0) * t,
17245
17767
  ratio: 1 + (12 - 1) * t,
@@ -17248,8 +17770,29 @@ var compressionParams = (amount) => {
17248
17770
  release: 0.25 + (0.15 - 0.25) * t
17249
17771
  };
17250
17772
  };
17773
+ var EQ_LOW_FREQ = 200;
17774
+ var EQ_MID_FREQ = 1e3;
17775
+ var EQ_HIGH_FREQ = 5e3;
17776
+ var EQ_MID_Q = 1;
17777
+ var EQ_MAX_DB = 12;
17251
17778
  var createChannelStrip = (ctx, destination, options = {}) => {
17252
17779
  const input = ctx.createGain();
17780
+ const eqLow = ctx.createBiquadFilter();
17781
+ eqLow.type = "lowshelf";
17782
+ eqLow.frequency.value = EQ_LOW_FREQ;
17783
+ eqLow.gain.value = clamp5(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17784
+ const eqMid = ctx.createBiquadFilter();
17785
+ eqMid.type = "peaking";
17786
+ eqMid.frequency.value = EQ_MID_FREQ;
17787
+ eqMid.Q.value = EQ_MID_Q;
17788
+ eqMid.gain.value = clamp5(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17789
+ const eqHigh = ctx.createBiquadFilter();
17790
+ eqHigh.type = "highshelf";
17791
+ eqHigh.frequency.value = EQ_HIGH_FREQ;
17792
+ eqHigh.gain.value = clamp5(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17793
+ input.connect(eqLow);
17794
+ eqLow.connect(eqMid);
17795
+ eqMid.connect(eqHigh);
17253
17796
  const compressor = ctx.createDynamicsCompressor();
17254
17797
  const applyCompression = (amount) => {
17255
17798
  const p = compressionParams(amount);
@@ -17292,21 +17835,45 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17292
17835
  outR.connect(merger, 0, 1);
17293
17836
  const setWidth = (width) => {
17294
17837
  widthGain.gain.setTargetAtTime(
17295
- clamp4(width, 0, 200) / 100,
17838
+ clamp5(width, 0, 200) / 100,
17296
17839
  ctx.currentTime,
17297
17840
  0.02
17298
17841
  );
17299
17842
  };
17300
17843
  setWidth(options.width ?? 100);
17301
- input.connect(compressor);
17844
+ eqHigh.connect(compressor);
17302
17845
  compressor.connect(splitter);
17303
17846
  merger.connect(destination);
17304
17847
  return {
17305
17848
  input,
17849
+ setEqLow: (db) => {
17850
+ eqLow.gain.setTargetAtTime(
17851
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17852
+ ctx.currentTime,
17853
+ 0.02
17854
+ );
17855
+ },
17856
+ setEqMid: (db) => {
17857
+ eqMid.gain.setTargetAtTime(
17858
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17859
+ ctx.currentTime,
17860
+ 0.02
17861
+ );
17862
+ },
17863
+ setEqHigh: (db) => {
17864
+ eqHigh.gain.setTargetAtTime(
17865
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17866
+ ctx.currentTime,
17867
+ 0.02
17868
+ );
17869
+ },
17306
17870
  setCompression: applyCompression,
17307
17871
  setWidth,
17308
17872
  dispose: () => {
17309
17873
  input.disconnect();
17874
+ eqLow.disconnect();
17875
+ eqMid.disconnect();
17876
+ eqHigh.disconnect();
17310
17877
  compressor.disconnect();
17311
17878
  splitter.disconnect();
17312
17879
  mid.disconnect();
@@ -17377,25 +17944,6 @@ var createClipMeter = (ctx, source, options = {}) => {
17377
17944
  };
17378
17945
  };
17379
17946
 
17380
- // src/reverb.ts
17381
- var IMPULSE_DURATION_SEC = 2.2;
17382
- var IMPULSE_DECAY = 2.5;
17383
- var createReverbImpulse = (ctx) => {
17384
- const rate = ctx.sampleRate;
17385
- const length = Math.max(1, Math.floor(rate * IMPULSE_DURATION_SEC));
17386
- const impulse = ctx.createBuffer(2, length, rate);
17387
- for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
17388
- const data = impulse.getChannelData(ch);
17389
- for (let i2 = 0; i2 < length; i2++) {
17390
- const envelope = (1 - i2 / length) ** IMPULSE_DECAY;
17391
- data[i2] = (Math.random() * 2 - 1) * envelope;
17392
- }
17393
- }
17394
- return impulse;
17395
- };
17396
- var REVERB_MAX_WET = 0.6;
17397
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
17398
-
17399
17947
  // src/sf/SoundFont_drum.ts
17400
17948
  var touch = (map, key, ctor) => {
17401
17949
  if (!map.has(key)) map.set(key, new ctor());
@@ -17551,12 +18099,16 @@ var createDtmStudio = async (options = {}) => {
17551
18099
  const drumGain = audioCtx.createGain();
17552
18100
  drumGain.gain.value = options.drumVolume ?? 1;
17553
18101
  drumGain.connect(masterGain);
18102
+ let reverbDecaySec = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
18103
+ const reverbPreDelay = audioCtx.createDelay(MAX_REVERB_PREDELAY_MS / 1e3);
18104
+ reverbPreDelay.delayTime.value = (options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS) / 1e3;
17554
18105
  const reverbConvolver = audioCtx.createConvolver();
17555
- reverbConvolver.buffer = createReverbImpulse(audioCtx);
18106
+ reverbConvolver.buffer = createReverbImpulse(audioCtx, reverbDecaySec);
17556
18107
  reverbConvolver.normalize = true;
17557
18108
  const reverbWetGain = audioCtx.createGain();
17558
18109
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
17559
- masterGain.connect(reverbConvolver);
18110
+ masterGain.connect(reverbPreDelay);
18111
+ reverbPreDelay.connect(reverbConvolver);
17560
18112
  reverbConvolver.connect(reverbWetGain);
17561
18113
  const setReverbAmount = (amount) => {
17562
18114
  reverbWetGain.gain.setTargetAtTime(
@@ -17565,9 +18117,28 @@ var createDtmStudio = async (options = {}) => {
17565
18117
  0.02
17566
18118
  );
17567
18119
  };
18120
+ const setReverbDecay = (seconds) => {
18121
+ reverbDecaySec = Math.max(
18122
+ MIN_REVERB_DECAY_SEC,
18123
+ Math.min(MAX_REVERB_DECAY_SEC, seconds)
18124
+ );
18125
+ reverbConvolver.buffer = createReverbImpulse(audioCtx, reverbDecaySec);
18126
+ };
18127
+ const setReverbPreDelay = (ms) => {
18128
+ reverbPreDelay.delayTime.setTargetAtTime(
18129
+ Math.max(MIN_REVERB_PREDELAY_MS, Math.min(MAX_REVERB_PREDELAY_MS, ms)) / 1e3,
18130
+ audioCtx.currentTime,
18131
+ 0.02
18132
+ );
18133
+ };
17568
18134
  const finalMix = audioCtx.createGain();
17569
18135
  masterGain.connect(finalMix);
17570
18136
  reverbWetGain.connect(finalMix);
18137
+ const delayBus = createDelayBus(audioCtx, finalMix, {
18138
+ amount: options.delayAmount ?? 0,
18139
+ division: options.delayDivision ?? "8"
18140
+ });
18141
+ const setDelayAmount = (amount) => delayBus.setAmount(amount);
17571
18142
  const safetyLimiter = audioCtx.createDynamicsCompressor();
17572
18143
  safetyLimiter.threshold.value = -1;
17573
18144
  safetyLimiter.knee.value = 0;
@@ -17620,6 +18191,8 @@ var createDtmStudio = async (options = {}) => {
17620
18191
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
17621
18192
  // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
17622
18193
  reverbBus: reverbConvolver,
18194
+ // ボーカルトラック個別のディレイセンド(`e`トークン)先。
18195
+ delayBus: delayBus.input,
17623
18196
  // トラック単位チャンネルストリップ(コンプレッサー/ステレオワイド)の入口。
17624
18197
  // 楽器と同じ getChannelStrip キャッシュを共有するので、演奏トラックと歌詞トラックが
17625
18198
  // 同じ trackId を指していれば同じ処理が掛かる。
@@ -17900,8 +18473,20 @@ var createDtmStudio = async (options = {}) => {
17900
18473
  },
17901
18474
  reverbAmount: options.reverbAmount,
17902
18475
  onReverbChange: setReverbAmount,
18476
+ reverbDecay: options.reverbDecay,
18477
+ onReverbDecayChange: setReverbDecay,
18478
+ reverbPreDelay: options.reverbPreDelay,
18479
+ onReverbPreDelayChange: setReverbPreDelay,
18480
+ delayAmount: options.delayAmount,
18481
+ onDelayChange: setDelayAmount,
18482
+ delayDivision: options.delayDivision,
18483
+ onDelayDivisionChange: (division) => delayBus.setDivision(division),
18484
+ onBpmChange: (bpm) => delayBus.setBpm(bpm),
17903
18485
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
17904
18486
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18487
+ onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
18488
+ onTrackEqMidChange: (trackId, db) => getChannelStrip(trackId).setEqMid(db),
18489
+ onTrackEqHighChange: (trackId, db) => getChannelStrip(trackId).setEqHigh(db),
17905
18490
  clipMeter,
17906
18491
  ...dawOverrides,
17907
18492
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
@@ -18438,6 +19023,7 @@ var createDtmStudio = async (options = {}) => {
18438
19023
  mountedChordPlayers.length = 0;
18439
19024
  mountedEditors.length = 0;
18440
19025
  clipMeter.dispose();
19026
+ delayBus.dispose();
18441
19027
  for (const strip of channelStrips.values()) strip.dispose();
18442
19028
  channelStrips.clear();
18443
19029
  void audioCtx.close();
@@ -18479,6 +19065,9 @@ var createDtmStudio = async (options = {}) => {
18479
19065
  setMasterVolume,
18480
19066
  setVolume: setMasterVolume,
18481
19067
  setReverbAmount,
19068
+ setReverbDecay,
19069
+ setReverbPreDelay,
19070
+ setDelayAmount,
18482
19071
  dispose
18483
19072
  };
18484
19073
  };