@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.mjs CHANGED
@@ -2186,10 +2186,11 @@ var parseLyrics = (mml) => {
2186
2186
  let octave = 0;
2187
2187
  let vibrato = false;
2188
2188
  let reverb = 0;
2189
+ let delay = 0;
2189
2190
  let gender = 50;
2190
2191
  let breathiness = 50;
2191
2192
  const modelMatch = rest.match(
2192
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrgh]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2193
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghe]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2193
2194
  );
2194
2195
  let model = "";
2195
2196
  const metaTokens = [];
@@ -2258,6 +2259,13 @@ var parseLyrics = (mml) => {
2258
2259
  rest = rest.substring(hMatch[0].length).trim();
2259
2260
  continue;
2260
2261
  }
2262
+ const eMatch = rest.match(/^e(\d+)/i);
2263
+ if (eMatch) {
2264
+ delay = clamp(Number.parseInt(eMatch[1], 10), 0, 100);
2265
+ metaTokens.push(eMatch[0]);
2266
+ rest = rest.substring(eMatch[0].length).trim();
2267
+ continue;
2268
+ }
2261
2269
  break;
2262
2270
  }
2263
2271
  const lyricLines = [rest];
@@ -2274,6 +2282,7 @@ var parseLyrics = (mml) => {
2274
2282
  octave,
2275
2283
  vibrato,
2276
2284
  reverb,
2285
+ delay,
2277
2286
  gender,
2278
2287
  breathiness,
2279
2288
  syllables,
@@ -2361,7 +2370,7 @@ var FORMANTS = {
2361
2370
  N: [250, 1e3]
2362
2371
  };
2363
2372
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
2364
- var createKlattVoice = (ctx, destination, reverbBus) => {
2373
+ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
2365
2374
  const active = /* @__PURE__ */ new Set();
2366
2375
  const voice = (syllable, e) => {
2367
2376
  const t0 = ctx.currentTime + e.when;
@@ -2380,11 +2389,17 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2380
2389
  panner.connect(dest);
2381
2390
  out = panner;
2382
2391
  }
2383
- let sendGain = null;
2392
+ let reverbSendGain = null;
2384
2393
  if (reverbBus && e.reverbSend && e.reverbSend > 0 && panner) {
2385
- sendGain = ctx.createGain();
2386
- sendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2387
- panner.connect(sendGain).connect(reverbBus);
2394
+ reverbSendGain = ctx.createGain();
2395
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2396
+ panner.connect(reverbSendGain).connect(reverbBus);
2397
+ }
2398
+ let delaySendGain = null;
2399
+ if (delayBus && e.delaySend && e.delaySend > 0 && panner) {
2400
+ delaySendGain = ctx.createGain();
2401
+ delaySendGain.gain.value = Math.max(0, Math.min(1, e.delaySend));
2402
+ panner.connect(delaySendGain).connect(delayBus);
2388
2403
  }
2389
2404
  const osc = ctx.createOscillator();
2390
2405
  osc.type = "sawtooth";
@@ -2441,7 +2456,8 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2441
2456
  active.delete(osc);
2442
2457
  osc.disconnect();
2443
2458
  panner?.disconnect();
2444
- sendGain?.disconnect();
2459
+ reverbSendGain?.disconnect();
2460
+ delaySendGain?.disconnect();
2445
2461
  };
2446
2462
  };
2447
2463
  voice.stopAll = () => {
@@ -2757,7 +2773,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2757
2773
  return p;
2758
2774
  };
2759
2775
  const LEADCAP_S = 0.09;
2760
- const schedule = (r, t0, peak, pan, reverbSend = 0, destOverride) => {
2776
+ const schedule = (r, t0, peak, pan, reverbSend = 0, delaySend = 0, destOverride) => {
2761
2777
  const dest = destOverride ?? destination;
2762
2778
  let out = dest;
2763
2779
  let panner = null;
@@ -2767,11 +2783,17 @@ var createKoeVoice = async (ctx, destination, options) => {
2767
2783
  panner.connect(dest);
2768
2784
  out = panner;
2769
2785
  }
2770
- let sendGain = null;
2786
+ let reverbSendGain = null;
2771
2787
  if (options.reverbBus && reverbSend > 0 && panner) {
2772
- sendGain = ctx.createGain();
2773
- sendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2774
- panner.connect(sendGain).connect(options.reverbBus);
2788
+ reverbSendGain = ctx.createGain();
2789
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2790
+ panner.connect(reverbSendGain).connect(options.reverbBus);
2791
+ }
2792
+ let delaySendGain = null;
2793
+ if (options.delayBus && delaySend > 0 && panner) {
2794
+ delaySendGain = ctx.createGain();
2795
+ delaySendGain.gain.value = Math.max(0, Math.min(1, delaySend));
2796
+ panner.connect(delaySendGain).connect(options.delayBus);
2775
2797
  }
2776
2798
  const src = ctx.createBufferSource();
2777
2799
  src.buffer = r.audio;
@@ -2798,7 +2820,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2798
2820
  src.disconnect();
2799
2821
  env.disconnect();
2800
2822
  panner?.disconnect();
2801
- sendGain?.disconnect();
2823
+ reverbSendGain?.disconnect();
2824
+ delaySendGain?.disconnect();
2802
2825
  };
2803
2826
  };
2804
2827
  const model = (syllable, e) => {
@@ -2817,7 +2840,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2817
2840
  const pan = e.pan ?? 0;
2818
2841
  const durationMs = Math.max(60, e.duration * 1e3);
2819
2842
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2820
- if (r) schedule(r, t0, peak, pan, e.reverbSend, e.destination);
2843
+ if (r)
2844
+ schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
2821
2845
  });
2822
2846
  };
2823
2847
  model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato, expr) => {
@@ -2835,9 +2859,9 @@ var createKoeVoice = async (ctx, destination, options) => {
2835
2859
  const r = await renderInto(alias, pitch, dMs, vib, expr);
2836
2860
  return r ? keyOf(alias, pitch, dMs, vib, expr) : null;
2837
2861
  };
2838
- model.scheduleCached = (key, t0, peak, pan, reverbSend, dest) => {
2862
+ model.scheduleCached = (key, t0, peak, pan, reverbSend, delaySend, dest) => {
2839
2863
  const r = renderCache.get(key);
2840
- if (r) schedule(r, t0, peak, pan, reverbSend, dest);
2864
+ if (r) schedule(r, t0, peak, pan, reverbSend, delaySend, dest);
2841
2865
  };
2842
2866
  model.stopAll = () => {
2843
2867
  for (const src of active) {
@@ -2867,8 +2891,12 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2867
2891
  let streamSession = 0;
2868
2892
  const masterGain = ctx.createGain();
2869
2893
  masterGain.connect(destination);
2894
+ let masterVolumeScalar = 1;
2870
2895
  const loaded = /* @__PURE__ */ new Map([
2871
- [FALLBACK_MODEL, createKlattVoice(ctx, masterGain, options.reverbBus)]
2896
+ [
2897
+ FALLBACK_MODEL,
2898
+ createKlattVoice(ctx, masterGain, options.reverbBus, options.delayBus)
2899
+ ]
2872
2900
  ]);
2873
2901
  const loading = /* @__PURE__ */ new Map();
2874
2902
  const load2 = (model) => {
@@ -2889,7 +2917,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2889
2917
  worldlineScriptUrl: options.worldlineScriptUrl,
2890
2918
  lightweight: options.lightweight,
2891
2919
  voiceWorkerUrl: options.voiceWorkerUrl,
2892
- reverbBus: options.reverbBus
2920
+ reverbBus: options.reverbBus,
2921
+ delayBus: options.delayBus
2893
2922
  })
2894
2923
  ))().then((v) => {
2895
2924
  loaded.set(m, v);
@@ -3008,13 +3037,16 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3008
3037
  if (key) {
3009
3038
  const delay = ctx.currentTime - t0;
3010
3039
  if (delay < 0.05) {
3040
+ const dest = options.getTrackDestination?.(track.id ?? "");
3041
+ const effPeak = dest ? peak * masterVolumeScalar : peak;
3011
3042
  scheduleCached(
3012
3043
  key,
3013
3044
  t0,
3014
- peak,
3045
+ effPeak,
3015
3046
  track.pan,
3016
3047
  track.reverbSend,
3017
- options.getTrackDestination?.(track.id ?? "")
3048
+ track.delaySend,
3049
+ dest
3018
3050
  );
3019
3051
  opts?.onScheduled?.(track, note, t0);
3020
3052
  } else {
@@ -3027,16 +3059,19 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3027
3059
  })();
3028
3060
  } else {
3029
3061
  const when = t0 - ctx.currentTime;
3062
+ const dest = options.getTrackDestination?.(track.id ?? "");
3063
+ const effPeak = dest ? peak * masterVolumeScalar : peak;
3030
3064
  model(note.syllable, {
3031
3065
  trackId: track.id ?? "",
3032
3066
  pitch: note.pitch,
3033
3067
  velocity: 100,
3034
- volume: peak,
3068
+ volume: effPeak,
3035
3069
  when,
3036
3070
  duration: note.durationSec,
3037
3071
  pan: track.pan,
3038
3072
  reverbSend: track.reverbSend,
3039
- destination: options.getTrackDestination?.(track.id ?? "")
3073
+ delaySend: track.delaySend,
3074
+ destination: dest
3040
3075
  });
3041
3076
  opts?.onScheduled?.(track, note, t0);
3042
3077
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -3061,7 +3096,9 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3061
3096
  for (const v of loaded.values()) v.reset?.();
3062
3097
  };
3063
3098
  const setVolume = (gain) => {
3064
- masterGain.gain.value = Math.max(0, gain);
3099
+ const g = Math.max(0, gain);
3100
+ masterGain.gain.value = g;
3101
+ masterVolumeScalar = g;
3065
3102
  };
3066
3103
  return {
3067
3104
  loadModels,
@@ -3095,10 +3132,13 @@ var PITCH_MAP = {
3095
3132
  b: 11
3096
3133
  };
3097
3134
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3098
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|mode)=([\w-]+)/gi;
3135
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mode)=([\w-]+)/gi;
3099
3136
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3100
3137
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3101
3138
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3139
+ var TRACK_EQLOW_DIRECTIVE = /#t(\d+)eqlo=(-?\d+)/gi;
3140
+ var TRACK_EQMID_DIRECTIVE = /#t(\d+)eqmid=(-?\d+)/gi;
3141
+ var TRACK_EQHIGH_DIRECTIVE = /#t(\d+)eqhi=(-?\d+)/gi;
3102
3142
  var parseMmlMeta = (mml) => {
3103
3143
  const meta = {};
3104
3144
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -3115,6 +3155,17 @@ var parseMmlMeta = (mml) => {
3115
3155
  } else if (key === "reverb") {
3116
3156
  const rv = Number.parseInt(m[2], 10);
3117
3157
  if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3158
+ } else if (key === "reverbdecay") {
3159
+ const rd = Number.parseInt(m[2], 10);
3160
+ if (!Number.isNaN(rd)) meta.reverbDecay = clamp2(rd, 3, 40);
3161
+ } else if (key === "reverbpredelay") {
3162
+ const rp = Number.parseInt(m[2], 10);
3163
+ if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp2(rp, 0, 150);
3164
+ } else if (key === "delay") {
3165
+ const dv = Number.parseInt(m[2], 10);
3166
+ if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3167
+ } else if (key === "delaydiv") {
3168
+ if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3118
3169
  } else if (key === "mode") {
3119
3170
  if (m[2] === "simple" || m[2] === "advanced") {
3120
3171
  meta.mode = m[2];
@@ -3145,9 +3196,33 @@ var parseMmlMeta = (mml) => {
3145
3196
  meta.trackWidth[idx] = val;
3146
3197
  }
3147
3198
  }
3199
+ for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3200
+ const idx = Number.parseInt(m[1], 10);
3201
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3202
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3203
+ meta.trackEqLow ??= {};
3204
+ meta.trackEqLow[idx] = val;
3205
+ }
3206
+ }
3207
+ for (const m of mml.matchAll(TRACK_EQMID_DIRECTIVE)) {
3208
+ const idx = Number.parseInt(m[1], 10);
3209
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3210
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3211
+ meta.trackEqMid ??= {};
3212
+ meta.trackEqMid[idx] = val;
3213
+ }
3214
+ }
3215
+ for (const m of mml.matchAll(TRACK_EQHIGH_DIRECTIVE)) {
3216
+ const idx = Number.parseInt(m[1], 10);
3217
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3218
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3219
+ meta.trackEqHigh ??= {};
3220
+ meta.trackEqHigh[idx] = val;
3221
+ }
3222
+ }
3148
3223
  return meta;
3149
3224
  };
3150
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "");
3225
+ 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, "");
3151
3226
  var formatMmlMeta = (meta, space = "") => {
3152
3227
  const parts = [];
3153
3228
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3158,6 +3233,14 @@ var formatMmlMeta = (meta, space = "") => {
3158
3233
  parts.push(`#drumvolume=${meta.drumVolume}`);
3159
3234
  if (meta.reverb !== void 0 && meta.reverb !== 0)
3160
3235
  parts.push(`#reverb=${meta.reverb}`);
3236
+ if (meta.reverbDecay !== void 0 && meta.reverbDecay !== 22)
3237
+ parts.push(`#reverbdecay=${meta.reverbDecay}`);
3238
+ if (meta.reverbPreDelay !== void 0 && meta.reverbPreDelay !== 0)
3239
+ parts.push(`#reverbpredelay=${meta.reverbPreDelay}`);
3240
+ if (meta.delay !== void 0 && meta.delay !== 0)
3241
+ parts.push(`#delay=${meta.delay}`);
3242
+ if (meta.delayDivision && meta.delayDivision !== "8")
3243
+ parts.push(`#delaydiv=${meta.delayDivision}`);
3161
3244
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3162
3245
  if (meta.trackInstruments) {
3163
3246
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
@@ -3174,6 +3257,21 @@ var formatMmlMeta = (meta, space = "") => {
3174
3257
  if (val !== 100) parts.push(`#t${idx}width=${val}`);
3175
3258
  }
3176
3259
  }
3260
+ if (meta.trackEqLow) {
3261
+ for (const [idx, val] of Object.entries(meta.trackEqLow)) {
3262
+ if (val !== 0) parts.push(`#t${idx}eqlo=${val}`);
3263
+ }
3264
+ }
3265
+ if (meta.trackEqMid) {
3266
+ for (const [idx, val] of Object.entries(meta.trackEqMid)) {
3267
+ if (val !== 0) parts.push(`#t${idx}eqmid=${val}`);
3268
+ }
3269
+ }
3270
+ if (meta.trackEqHigh) {
3271
+ for (const [idx, val] of Object.entries(meta.trackEqHigh)) {
3272
+ if (val !== 0) parts.push(`#t${idx}eqhi=${val}`);
3273
+ }
3274
+ }
3177
3275
  return parts.join(space);
3178
3276
  };
3179
3277
  var parseMML = (mml, options = {}) => {
@@ -9009,6 +9107,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9009
9107
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
9010
9108
  vibrato: lt.vibrato,
9011
9109
  reverbSend: (lt.reverb ?? 0) / 100,
9110
+ delaySend: (lt.delay ?? 0) / 100,
9012
9111
  gender: (lt.gender ?? 50) / 100,
9013
9112
  breathiness: (lt.breathiness ?? 50) / 100,
9014
9113
  notes
@@ -10688,6 +10787,71 @@ var mountChordPlayer = (target, chords, options = {}) => {
10688
10787
  };
10689
10788
  };
10690
10789
 
10790
+ // src/delay.ts
10791
+ var DELAY_DIVISIONS = [
10792
+ { value: "4", label: "4\u5206", beats: 1 },
10793
+ { value: "8", label: "8\u5206", beats: 0.5 },
10794
+ { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
10795
+ { value: "16", label: "16\u5206", beats: 0.25 }
10796
+ ];
10797
+ var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
10798
+ var divisionToSeconds = (division, bpm) => {
10799
+ const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
10800
+ const safeBpm = bpm > 0 ? bpm : 120;
10801
+ return 60 / safeBpm * beats;
10802
+ };
10803
+ var DELAY_MAX_WET = 0.45;
10804
+ var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
10805
+ var FEEDBACK_GAIN = 0.3;
10806
+ var MAX_DELAY_SEC = 2;
10807
+ var createDelayBus = (ctx, destination, options = {}) => {
10808
+ const input = ctx.createGain();
10809
+ const delayNode = ctx.createDelay(MAX_DELAY_SEC);
10810
+ const feedback = ctx.createGain();
10811
+ feedback.gain.value = FEEDBACK_GAIN;
10812
+ const wetGain = ctx.createGain();
10813
+ wetGain.gain.value = delayAmountToGain(options.amount ?? 0);
10814
+ input.connect(delayNode);
10815
+ delayNode.connect(feedback);
10816
+ feedback.connect(delayNode);
10817
+ delayNode.connect(wetGain);
10818
+ wetGain.connect(destination);
10819
+ let bpm = options.bpm ?? 120;
10820
+ let division = options.division ?? "8";
10821
+ const applyTime = () => {
10822
+ delayNode.delayTime.setTargetAtTime(
10823
+ divisionToSeconds(division, bpm),
10824
+ ctx.currentTime,
10825
+ 0.05
10826
+ );
10827
+ };
10828
+ applyTime();
10829
+ return {
10830
+ input,
10831
+ setAmount: (amount) => {
10832
+ wetGain.gain.setTargetAtTime(
10833
+ delayAmountToGain(amount),
10834
+ ctx.currentTime,
10835
+ 0.02
10836
+ );
10837
+ },
10838
+ setDivision: (d) => {
10839
+ division = d;
10840
+ applyTime();
10841
+ },
10842
+ setBpm: (b) => {
10843
+ bpm = b;
10844
+ applyTime();
10845
+ },
10846
+ dispose: () => {
10847
+ input.disconnect();
10848
+ delayNode.disconnect();
10849
+ feedback.disconnect();
10850
+ wetGain.disconnect();
10851
+ }
10852
+ };
10853
+ };
10854
+
10691
10855
  // src/daw-ui.ts
10692
10856
  var q = (root, sel) => root.querySelector(sel);
10693
10857
  var buildUI = (target, options) => {
@@ -10804,6 +10968,25 @@ var buildUI = (target, options) => {
10804
10968
  <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10805
10969
  <button class="dtm-infobtn" data-dtm="reverb-amount-info" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10806
10970
  </div>
10971
+ <div class="dtm-row">
10972
+ <span class="dtm-label">Decay</span>
10973
+ <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">
10974
+ <span class="dtm-label" data-dtm="reverb-decay-label">2.2s</span>
10975
+ </div>
10976
+ <div class="dtm-row">
10977
+ <span class="dtm-label">Pre Delay</span>
10978
+ <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">
10979
+ <span class="dtm-label" data-dtm="reverb-predelay-label">0ms</span>
10980
+ </div>
10981
+ <div class="dtm-row">
10982
+ <span class="dtm-label">\u30C7\u30A3\u30EC\u30A4</span>
10983
+ <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">
10984
+ <span class="dtm-label" data-dtm="delay-amount-label">0%</span>
10985
+ <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">
10986
+ ${DELAY_DIVISIONS.map((d) => `<option value="${d.value}">${d.label}</option>`).join("")}
10987
+ </select>
10988
+ <button class="dtm-infobtn" data-dtm="delay-amount-info" title="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10989
+ </div>
10807
10990
  </div>
10808
10991
  </details>
10809
10992
 
@@ -11002,6 +11185,14 @@ var buildUI = (target, options) => {
11002
11185
  reverbAmount: sel("reverb-amount"),
11003
11186
  reverbAmountLabel: sel("reverb-amount-label"),
11004
11187
  reverbAmountInfoBtn: sel("reverb-amount-info"),
11188
+ reverbDecay: sel("reverb-decay"),
11189
+ reverbDecayLabel: sel("reverb-decay-label"),
11190
+ reverbPreDelay: sel("reverb-predelay"),
11191
+ reverbPreDelayLabel: sel("reverb-predelay-label"),
11192
+ delayAmount: sel("delay-amount"),
11193
+ delayAmountLabel: sel("delay-amount-label"),
11194
+ delayAmountInfoBtn: sel("delay-amount-info"),
11195
+ delayDivision: sel("delay-division"),
11005
11196
  autoMasterBtn: sel("auto-master"),
11006
11197
  autoMasterInfoBtn: sel("auto-master-info"),
11007
11198
  trackTabs: sel("track-tabs"),
@@ -12635,6 +12826,38 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
12635
12826
  return chordNotes / notes.length >= threshold;
12636
12827
  };
12637
12828
 
12829
+ // src/reverb.ts
12830
+ var DEFAULT_REVERB_DECAY_SEC = 2.2;
12831
+ var MIN_REVERB_DECAY_SEC = 0.3;
12832
+ var MAX_REVERB_DECAY_SEC = 4;
12833
+ var IMPULSE_DECAY_CURVE = 2.5;
12834
+ var DEFAULT_REVERB_PREDELAY_MS = 0;
12835
+ var MIN_REVERB_PREDELAY_MS = 0;
12836
+ var MAX_REVERB_PREDELAY_MS = 150;
12837
+ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
12838
+ const rate = ctx.sampleRate;
12839
+ const length = Math.max(
12840
+ 1,
12841
+ Math.floor(
12842
+ rate * Math.max(
12843
+ MIN_REVERB_DECAY_SEC,
12844
+ Math.min(MAX_REVERB_DECAY_SEC, decaySec)
12845
+ )
12846
+ )
12847
+ );
12848
+ const impulse = ctx.createBuffer(2, length, rate);
12849
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
12850
+ const data = impulse.getChannelData(ch);
12851
+ for (let i2 = 0; i2 < length; i2++) {
12852
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
12853
+ data[i2] = (Math.random() * 2 - 1) * envelope;
12854
+ }
12855
+ }
12856
+ return impulse;
12857
+ };
12858
+ var REVERB_MAX_WET = 0.6;
12859
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
12860
+
12638
12861
  // src/daw.ts
12639
12862
  var CHORD_INFO_HTML2 = `
12640
12863
  <div class="dtm-modal-body-content">
@@ -12706,14 +12929,46 @@ var LYRIC_REVERB_INFO_HTML = `
12706
12929
  <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>
12707
12930
  </div>
12708
12931
  `;
12709
- var MASTER_REVERB_INFO_HTML = `
12932
+ var LYRIC_DELAY_INFO_HTML = `
12710
12933
  <div class="dtm-modal-body-content">
12711
12934
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12712
- <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>
12935
+ <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>
12936
+ <h4>\u30DE\u30B9\u30BF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u300D\u3064\u307E\u307F\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12937
+ <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>
12938
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
12939
+ <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>
12940
+ </div>
12941
+ `;
12942
+ var MASTER_REVERB_INFO_HTML = `
12943
+ <div class="dtm-modal-body-content">
12944
+ <h4>\u30EA\u30D0\u30FC\u30D6\uFF08Mix\uFF09</h4>
12945
+ <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>
12946
+ <h4>Decay\uFF08\u6B8B\u97FF\u306E\u9577\u3055\uFF09</h4>
12947
+ <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>
12948
+ <h4>Pre Delay\uFF08\u539F\u97F3\u3068\u30EA\u30D0\u30FC\u30D6\u306E\u9593\u9694\uFF09</h4>
12949
+ <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>
12713
12950
  <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12714
- <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>
12715
- <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12716
- <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>
12951
+ <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>
12952
+ </div>
12953
+ `;
12954
+ var MASTER_DELAY_INFO_HTML = `
12955
+ <div class="dtm-modal-body-content">
12956
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12957
+ <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>
12958
+ <h4>\u30EA\u30D0\u30FC\u30D6\u3068\u306E\u9055\u3044</h4>
12959
+ <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>
12960
+ <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2</h4>
12961
+ <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>
12962
+ </div>
12963
+ `;
12964
+ var TRACK_EQ_INFO_HTML = `
12965
+ <div class="dtm-modal-body-content">
12966
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12967
+ <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>
12968
+ <h4>\u4F7F\u3044\u3069\u3053\u308D\uFF08\u5E2F\u57DF\u306E\u68F2\u307F\u5206\u3051\uFF09</h4>
12969
+ <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>
12970
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044\uFF08\u9806\u5E8F\u304C\u91CD\u8981\uFF09</h4>
12971
+ <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>
12717
12972
  </div>
12718
12973
  `;
12719
12974
  var TRACK_COMPRESSION_INFO_HTML = `
@@ -12739,14 +12994,22 @@ var TRACK_WIDTH_INFO_HTML = `
12739
12994
  var AUTO_MASTER_INFO_HTML = `
12740
12995
  <div class="dtm-modal-body-content">
12741
12996
  <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
12742
- <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>
12997
+ <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>
12743
12998
  <ul>
12744
- <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6: 20%</li>
12745
- <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: 35%</li>
12999
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Mix: 20%</li>
13000
+ <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>
13001
+ <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>
13002
+ <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>
12746
13003
  <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45: 115%\uFF08\u308F\u305A\u304B\u306B\u5E83\u3052\u308B\uFF09</li>
12747
- <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>
13004
+ <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON</li>
12748
13005
  </ul>
12749
- <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>
13006
+ <h4>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB\u306E\u81EA\u52D5\u5224\u5B9A</h4>
13007
+ <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>
13008
+ <ul>
13009
+ <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>
13010
+ <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>
13011
+ </ul>
13012
+ <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>
12750
13013
  </div>
12751
13014
  `;
12752
13015
  var MIDI_INFO_HTML = `
@@ -12982,7 +13245,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
12982
13245
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
12983
13246
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
12984
13247
  };
12985
- var clamp3 = (v, min, max) => Math.min(Math.max(v, min), max);
13248
+ var clamp4 = (v, min, max) => Math.min(Math.max(v, min), max);
12986
13249
  var normalizeInstrumentName = (name) => {
12987
13250
  if (!name) return "";
12988
13251
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13022,6 +13285,17 @@ var mountDAW = (target, options = {}) => {
13022
13285
  refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
13023
13286
  refs.reverbAmount.value = String(options.reverbAmount ?? 0);
13024
13287
  refs.reverbAmountLabel.textContent = `${options.reverbAmount ?? 0}%`;
13288
+ {
13289
+ const initDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
13290
+ const initPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13291
+ refs.reverbDecay.value = String(Math.round(initDecay * 10));
13292
+ refs.reverbDecayLabel.textContent = `${initDecay.toFixed(1)}s`;
13293
+ refs.reverbPreDelay.value = String(initPreDelay);
13294
+ refs.reverbPreDelayLabel.textContent = `${initPreDelay}ms`;
13295
+ }
13296
+ refs.delayAmount.value = String(options.delayAmount ?? 0);
13297
+ refs.delayAmountLabel.textContent = `${options.delayAmount ?? 0}%`;
13298
+ refs.delayDivision.value = options.delayDivision ?? "8";
13025
13299
  refs.drumVolume.value = String(options.drumVolume ?? 80);
13026
13300
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
13027
13301
  const renderConfig = {
@@ -13038,8 +13312,16 @@ var mountDAW = (target, options = {}) => {
13038
13312
  let masterVolume = options.masterVolume ?? 50;
13039
13313
  options.singingVoices?.setVolume(masterVolume / 100);
13040
13314
  let reverbAmount = options.reverbAmount ?? 0;
13315
+ let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
13316
+ let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13317
+ let delayAmount = options.delayAmount ?? 0;
13318
+ let delayDivision = options.delayDivision ?? "8";
13041
13319
  let unsubscribeClip;
13042
13320
  options.onReverbChange?.(reverbAmount);
13321
+ options.onReverbDecayChange?.(reverbDecay);
13322
+ options.onReverbPreDelayChange?.(reverbPreDelay);
13323
+ options.onDelayChange?.(delayAmount);
13324
+ options.onDelayDivisionChange?.(delayDivision);
13043
13325
  let drumVolume = options.drumVolume ?? 80;
13044
13326
  let currentDrumPattern = refs.drumSelect.value;
13045
13327
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
@@ -13120,6 +13402,7 @@ var mountDAW = (target, options = {}) => {
13120
13402
  vocalOctave: t.vocalOctave,
13121
13403
  vocalVibrato: t.vocalVibrato,
13122
13404
  vocalReverb: t.vocalReverb,
13405
+ vocalDelay: t.vocalDelay,
13123
13406
  vocalGender: t.vocalGender,
13124
13407
  vocalBreathiness: t.vocalBreathiness
13125
13408
  };
@@ -13183,11 +13466,15 @@ var mountDAW = (target, options = {}) => {
13183
13466
  vocalOctave: 0,
13184
13467
  vocalVibrato: false,
13185
13468
  vocalReverb: 0,
13469
+ vocalDelay: 0,
13186
13470
  vocalGender: 50,
13187
13471
  vocalBreathiness: 50,
13188
13472
  trackInstrument: "",
13189
13473
  trackCompression: 0,
13190
- trackWidth: 100
13474
+ trackWidth: 100,
13475
+ trackEqLow: 0,
13476
+ trackEqMid: 0,
13477
+ trackEqHigh: 0
13191
13478
  };
13192
13479
  });
13193
13480
  };
@@ -13208,6 +13495,7 @@ var mountDAW = (target, options = {}) => {
13208
13495
  octave: t.vocalOctave,
13209
13496
  vibrato: t.vocalVibrato,
13210
13497
  reverb: t.vocalReverb,
13498
+ delay: t.vocalDelay,
13211
13499
  gender: t.vocalGender,
13212
13500
  breathiness: t.vocalBreathiness,
13213
13501
  syllables
@@ -13330,7 +13618,7 @@ var mountDAW = (target, options = {}) => {
13330
13618
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13331
13619
  const ratio = currentOffsetX / maxOffsetX;
13332
13620
  refs.hScrollThumb.style.width = `${thumbW}px`;
13333
- refs.hScrollThumb.style.left = `${clamp3(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13621
+ refs.hScrollThumb.style.left = `${clamp4(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13334
13622
  }
13335
13623
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13336
13624
  const sbH = refs.vScroll.clientHeight;
@@ -13400,9 +13688,9 @@ var mountDAW = (target, options = {}) => {
13400
13688
  if (maxOffsetX <= 0) return;
13401
13689
  const rect = refs.hScroll.getBoundingClientRect();
13402
13690
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13403
- const x2 = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13691
+ const x2 = clamp4(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13404
13692
  const ratio = x2 / (rect.width - thumbW);
13405
- currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
13693
+ currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
13406
13694
  setDrawOffset(currentOffsetX, currentOffsetY);
13407
13695
  redrawAll();
13408
13696
  };
@@ -13411,9 +13699,9 @@ var mountDAW = (target, options = {}) => {
13411
13699
  if (maxOffset <= 0) return;
13412
13700
  const rect = refs.vScroll.getBoundingClientRect();
13413
13701
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
13414
- const y = clamp3(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13702
+ const y = clamp4(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13415
13703
  const ratio = y / (rect.height - thumbH);
13416
- currentOffsetY = clamp3(ratio * maxOffset, 0, maxOffset);
13704
+ currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
13417
13705
  setDrawOffset(currentOffsetX, currentOffsetY);
13418
13706
  redrawAll();
13419
13707
  };
@@ -13460,8 +13748,8 @@ var mountDAW = (target, options = {}) => {
13460
13748
  if (dx !== 0 || dy !== 0) {
13461
13749
  const maxOffsetX = getMaxOffsetX();
13462
13750
  const maxOffsetY = getMaxOffsetY();
13463
- currentOffsetX = clamp3(currentOffsetX + dx, 0, maxOffsetX);
13464
- currentOffsetY = clamp3(currentOffsetY + dy, 0, maxOffsetY);
13751
+ currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
13752
+ currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
13465
13753
  setDrawOffset(currentOffsetX, currentOffsetY);
13466
13754
  onPointerMove(lastMoveEvent);
13467
13755
  }
@@ -13851,12 +14139,12 @@ var mountDAW = (target, options = {}) => {
13851
14139
  "wheel",
13852
14140
  (event) => {
13853
14141
  event.preventDefault();
13854
- currentOffsetY = clamp3(
14142
+ currentOffsetY = clamp4(
13855
14143
  currentOffsetY + event.deltaY,
13856
14144
  0,
13857
14145
  getMaxOffsetY()
13858
14146
  );
13859
- currentOffsetX = clamp3(
14147
+ currentOffsetX = clamp4(
13860
14148
  currentOffsetX + event.deltaX,
13861
14149
  0,
13862
14150
  getMaxOffsetX()
@@ -13895,7 +14183,7 @@ var mountDAW = (target, options = {}) => {
13895
14183
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
13896
14184
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
13897
14185
  refs.zoomXLabel.textContent = `${zoomX}%`;
13898
- currentOffsetX = clamp3(
14186
+ currentOffsetX = clamp4(
13899
14187
  centerStep * renderConfig.stepWidth - canvas.width / 2,
13900
14188
  0,
13901
14189
  getMaxOffsetX()
@@ -13908,7 +14196,7 @@ var mountDAW = (target, options = {}) => {
13908
14196
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
13909
14197
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
13910
14198
  refs.zoomYLabel.textContent = `${zoomY}%`;
13911
- currentOffsetY = clamp3(
14199
+ currentOffsetY = clamp4(
13912
14200
  centerKey * renderConfig.keyHeight - canvas.height / 2,
13913
14201
  0,
13914
14202
  getMaxOffsetY()
@@ -13958,7 +14246,7 @@ var mountDAW = (target, options = {}) => {
13958
14246
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
13959
14247
  if (currentPlayStep > threshold) {
13960
14248
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
13961
- currentOffsetX = clamp3(
14249
+ currentOffsetX = clamp4(
13962
14250
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
13963
14251
  0,
13964
14252
  getMaxOffsetX()
@@ -14014,6 +14302,7 @@ var mountDAW = (target, options = {}) => {
14014
14302
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
14015
14303
  vibrato: lt.vibrato,
14016
14304
  reverbSend: (lt.reverb ?? 0) / 100,
14305
+ delaySend: (lt.delay ?? 0) / 100,
14017
14306
  gender: (lt.gender ?? 50) / 100,
14018
14307
  breathiness: (lt.breathiness ?? 50) / 100,
14019
14308
  notes
@@ -14043,7 +14332,7 @@ var mountDAW = (target, options = {}) => {
14043
14332
  }
14044
14333
  if (playbackState !== "paused") {
14045
14334
  const canvas = getGridCanvas();
14046
- currentOffsetX = clamp3(
14335
+ currentOffsetX = clamp4(
14047
14336
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14048
14337
  0,
14049
14338
  getMaxOffsetX()
@@ -14123,7 +14412,23 @@ var mountDAW = (target, options = {}) => {
14123
14412
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14124
14413
  </div>
14125
14414
  <details class="dtm-advanced" data-dtm="track-fx-advanced">
14126
- <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14415
+ <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14416
+ <div class="dtm-row">
14417
+ <span class="dtm-label">EQ\u4F4E\u57DF</span>
14418
+ <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">
14419
+ <span class="dtm-label" data-dtm="track-eq-low-label"></span>
14420
+ <button class="dtm-infobtn" data-dtm="track-eq-info" title="EQ\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14421
+ </div>
14422
+ <div class="dtm-row">
14423
+ <span class="dtm-label">EQ\u4E2D\u57DF</span>
14424
+ <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">
14425
+ <span class="dtm-label" data-dtm="track-eq-mid-label"></span>
14426
+ </div>
14427
+ <div class="dtm-row">
14428
+ <span class="dtm-label">EQ\u9AD8\u57DF</span>
14429
+ <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">
14430
+ <span class="dtm-label" data-dtm="track-eq-high-label"></span>
14431
+ </div>
14127
14432
  <div class="dtm-row">
14128
14433
  <span class="dtm-label">\u97F3\u5727\u5F37\u5316</span>
14129
14434
  <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">
@@ -14153,6 +14458,52 @@ var mountDAW = (target, options = {}) => {
14153
14458
  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" : "";
14154
14459
  };
14155
14460
  syncVelocityDisabled();
14461
+ const trackEqLowInput = refs.trackBody.querySelector(
14462
+ '[data-dtm="track-eq-low"]'
14463
+ );
14464
+ const trackEqLowLabel = refs.trackBody.querySelector(
14465
+ '[data-dtm="track-eq-low-label"]'
14466
+ );
14467
+ const trackEqMidInput = refs.trackBody.querySelector(
14468
+ '[data-dtm="track-eq-mid"]'
14469
+ );
14470
+ const trackEqMidLabel = refs.trackBody.querySelector(
14471
+ '[data-dtm="track-eq-mid-label"]'
14472
+ );
14473
+ const trackEqHighInput = refs.trackBody.querySelector(
14474
+ '[data-dtm="track-eq-high"]'
14475
+ );
14476
+ const trackEqHighLabel = refs.trackBody.querySelector(
14477
+ '[data-dtm="track-eq-high-label"]'
14478
+ );
14479
+ const trackEqInfo = refs.trackBody.querySelector(
14480
+ '[data-dtm="track-eq-info"]'
14481
+ );
14482
+ const fmtDb = (db) => db > 0 ? `+${db}dB` : `${db}dB`;
14483
+ trackEqLowInput.value = String(active.trackEqLow);
14484
+ trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
14485
+ trackEqMidInput.value = String(active.trackEqMid);
14486
+ trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
14487
+ trackEqHighInput.value = String(active.trackEqHigh);
14488
+ trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
14489
+ trackEqLowInput.addEventListener("input", () => {
14490
+ active.trackEqLow = Number.parseInt(trackEqLowInput.value, 10);
14491
+ trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
14492
+ options.onTrackEqLowChange?.(active.config.id, active.trackEqLow);
14493
+ });
14494
+ trackEqMidInput.addEventListener("input", () => {
14495
+ active.trackEqMid = Number.parseInt(trackEqMidInput.value, 10);
14496
+ trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
14497
+ options.onTrackEqMidChange?.(active.config.id, active.trackEqMid);
14498
+ });
14499
+ trackEqHighInput.addEventListener("input", () => {
14500
+ active.trackEqHigh = Number.parseInt(trackEqHighInput.value, 10);
14501
+ trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
14502
+ options.onTrackEqHighChange?.(active.config.id, active.trackEqHigh);
14503
+ });
14504
+ trackEqInfo.addEventListener("click", () => {
14505
+ showModal("EQ\uFF08\u30A4\u30B3\u30E9\u30A4\u30B6\u30FC\uFF09\u306E\u89E3\u8AAC", TRACK_EQ_INFO_HTML);
14506
+ });
14156
14507
  const trackCompInput = refs.trackBody.querySelector(
14157
14508
  '[data-dtm="track-comp"]'
14158
14509
  );
@@ -14305,6 +14656,12 @@ var mountDAW = (target, options = {}) => {
14305
14656
  <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14306
14657
  <button class="dtm-infobtn" data-dtm="lyric-reverb-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14307
14658
  </div>
14659
+ <div class="dtm-row">
14660
+ <span class="dtm-label">\u30C7\u30A3\u30EC\u30A4\u9001\u308A</span>
14661
+ <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">
14662
+ <span class="dtm-label" data-dtm="lyric-delay-label"></span>
14663
+ <button class="dtm-infobtn" data-dtm="lyric-delay-info" title="\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14664
+ </div>
14308
14665
  <div class="dtm-row">
14309
14666
  <span class="dtm-label">\u30B8\u30A7\u30F3\u30C0\u30FC</span>
14310
14667
  <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">
@@ -14369,6 +14726,18 @@ var mountDAW = (target, options = {}) => {
14369
14726
  lyricReverbInfo.addEventListener("click", () => {
14370
14727
  showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_REVERB_INFO_HTML);
14371
14728
  });
14729
+ const lyricDelay = lyricDiv.querySelector(
14730
+ '[data-dtm="lyric-delay"]'
14731
+ );
14732
+ const lyricDelayLabel = lyricDiv.querySelector(
14733
+ '[data-dtm="lyric-delay-label"]'
14734
+ );
14735
+ const lyricDelayInfo = lyricDiv.querySelector(
14736
+ '[data-dtm="lyric-delay-info"]'
14737
+ );
14738
+ lyricDelayInfo.addEventListener("click", () => {
14739
+ showModal("\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_DELAY_INFO_HTML);
14740
+ });
14372
14741
  const lyricGender = lyricDiv.querySelector(
14373
14742
  '[data-dtm="lyric-gender"]'
14374
14743
  );
@@ -14465,6 +14834,8 @@ var mountDAW = (target, options = {}) => {
14465
14834
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14466
14835
  lyricReverb.value = String(active.vocalReverb);
14467
14836
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14837
+ lyricDelay.value = String(active.vocalDelay);
14838
+ lyricDelayLabel.textContent = `${active.vocalDelay}%`;
14468
14839
  lyricGender.value = String(active.vocalGender);
14469
14840
  lyricGenderLabel.textContent = `${active.vocalGender}`;
14470
14841
  lyricBreathiness.value = String(active.vocalBreathiness);
@@ -14619,6 +14990,11 @@ var mountDAW = (target, options = {}) => {
14619
14990
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14620
14991
  fireLyricsChange(active);
14621
14992
  });
14993
+ lyricDelay.addEventListener("input", () => {
14994
+ active.vocalDelay = Number.parseInt(lyricDelay.value, 10);
14995
+ lyricDelayLabel.textContent = `${active.vocalDelay}%`;
14996
+ fireLyricsChange(active);
14997
+ });
14622
14998
  lyricGender.addEventListener("input", () => {
14623
14999
  active.vocalGender = Number.parseInt(lyricGender.value, 10);
14624
15000
  lyricGenderLabel.textContent = `${active.vocalGender}`;
@@ -14714,15 +15090,24 @@ var mountDAW = (target, options = {}) => {
14714
15090
  const trackInstrumentsForMeta = {};
14715
15091
  const trackCompressionForMeta = {};
14716
15092
  const trackWidthForMeta = {};
15093
+ const trackEqLowForMeta = {};
15094
+ const trackEqMidForMeta = {};
15095
+ const trackEqHighForMeta = {};
14717
15096
  trackStates.forEach((t, i2) => {
14718
15097
  if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14719
15098
  if (t.trackCompression !== 0)
14720
15099
  trackCompressionForMeta[i2] = t.trackCompression;
14721
15100
  if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
15101
+ if (t.trackEqLow !== 0) trackEqLowForMeta[i2] = t.trackEqLow;
15102
+ if (t.trackEqMid !== 0) trackEqMidForMeta[i2] = t.trackEqMid;
15103
+ if (t.trackEqHigh !== 0) trackEqHighForMeta[i2] = t.trackEqHigh;
14722
15104
  });
14723
15105
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14724
15106
  const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
14725
15107
  const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
15108
+ const trackEqLowMeta = Object.keys(trackEqLowForMeta).length > 0 ? trackEqLowForMeta : void 0;
15109
+ const trackEqMidMeta = Object.keys(trackEqMidForMeta).length > 0 ? trackEqMidForMeta : void 0;
15110
+ const trackEqHighMeta = Object.keys(trackEqHighForMeta).length > 0 ? trackEqHighForMeta : void 0;
14726
15111
  const metaLineFull = formatMmlMeta(
14727
15112
  {
14728
15113
  instrument: currentInstrument || void 0,
@@ -14730,10 +15115,17 @@ var mountDAW = (target, options = {}) => {
14730
15115
  volume: masterVolume,
14731
15116
  drumVolume,
14732
15117
  reverb: reverbAmount,
15118
+ reverbDecay: Math.round(reverbDecay * 10),
15119
+ reverbPreDelay,
15120
+ delay: delayAmount,
15121
+ delayDivision,
14733
15122
  mode,
14734
15123
  trackInstruments: trackInstMeta,
14735
15124
  trackCompression: trackCompMeta,
14736
- trackWidth: trackWidthMeta
15125
+ trackWidth: trackWidthMeta,
15126
+ trackEqLow: trackEqLowMeta,
15127
+ trackEqMid: trackEqMidMeta,
15128
+ trackEqHigh: trackEqHighMeta
14737
15129
  },
14738
15130
  " "
14739
15131
  );
@@ -14744,10 +15136,17 @@ var mountDAW = (target, options = {}) => {
14744
15136
  volume: masterVolume,
14745
15137
  drumVolume,
14746
15138
  reverb: reverbAmount,
15139
+ reverbDecay: Math.round(reverbDecay * 10),
15140
+ reverbPreDelay,
15141
+ delay: delayAmount,
15142
+ delayDivision,
14747
15143
  mode,
14748
15144
  trackInstruments: trackInstMeta,
14749
15145
  trackCompression: trackCompMeta,
14750
- trackWidth: trackWidthMeta
15146
+ trackWidth: trackWidthMeta,
15147
+ trackEqLow: trackEqLowMeta,
15148
+ trackEqMid: trackEqMidMeta,
15149
+ trackEqHigh: trackEqHighMeta
14751
15150
  },
14752
15151
  ""
14753
15152
  );
@@ -14797,6 +15196,7 @@ var mountDAW = (target, options = {}) => {
14797
15196
  oct: t.vocalOctave,
14798
15197
  vib: t.vocalVibrato,
14799
15198
  rev: t.vocalReverb,
15199
+ del: t.vocalDelay,
14800
15200
  gen: t.vocalGender,
14801
15201
  bre: t.vocalBreathiness
14802
15202
  })).filter(
@@ -14809,6 +15209,7 @@ var mountDAW = (target, options = {}) => {
14809
15209
  x2.oct === 0 ? "" : `o${x2.oct}`,
14810
15210
  x2.vib ? "b1" : "",
14811
15211
  x2.rev === 0 ? "" : `r${x2.rev}`,
15212
+ x2.del === 0 ? "" : `e${x2.del}`,
14812
15213
  x2.gen === 50 ? "" : `g${x2.gen}`,
14813
15214
  x2.bre === 50 ? "" : `h${x2.bre}`
14814
15215
  ].filter((s) => s.length > 0).join(" ");
@@ -14879,7 +15280,7 @@ var mountDAW = (target, options = {}) => {
14879
15280
  const canvas = getGridCanvas();
14880
15281
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
14881
15282
  const logicalY = yIndex * renderConfig.keyHeight;
14882
- currentOffsetY = clamp3(
15283
+ currentOffsetY = clamp4(
14883
15284
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
14884
15285
  0,
14885
15286
  getMaxOffsetY()
@@ -14956,6 +15357,29 @@ var mountDAW = (target, options = {}) => {
14956
15357
  refs.reverbAmountLabel.textContent = `${meta.reverb}%`;
14957
15358
  options.onReverbChange?.(meta.reverb);
14958
15359
  }
15360
+ if (meta.reverbDecay !== void 0) {
15361
+ reverbDecay = meta.reverbDecay / 10;
15362
+ refs.reverbDecay.value = String(meta.reverbDecay);
15363
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
15364
+ options.onReverbDecayChange?.(reverbDecay);
15365
+ }
15366
+ if (meta.reverbPreDelay !== void 0) {
15367
+ reverbPreDelay = meta.reverbPreDelay;
15368
+ refs.reverbPreDelay.value = String(meta.reverbPreDelay);
15369
+ refs.reverbPreDelayLabel.textContent = `${meta.reverbPreDelay}ms`;
15370
+ options.onReverbPreDelayChange?.(reverbPreDelay);
15371
+ }
15372
+ if (meta.delay !== void 0) {
15373
+ delayAmount = meta.delay;
15374
+ refs.delayAmount.value = String(meta.delay);
15375
+ refs.delayAmountLabel.textContent = `${meta.delay}%`;
15376
+ options.onDelayChange?.(meta.delay);
15377
+ }
15378
+ if (meta.delayDivision && ["4", "8", "8d", "16"].includes(meta.delayDivision)) {
15379
+ delayDivision = meta.delayDivision;
15380
+ refs.delayDivision.value = delayDivision;
15381
+ options.onDelayDivisionChange?.(delayDivision);
15382
+ }
14959
15383
  }
14960
15384
  trackStates.forEach((t, i2) => {
14961
15385
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -14977,6 +15401,21 @@ var mountDAW = (target, options = {}) => {
14977
15401
  t.trackWidth = width;
14978
15402
  options.onTrackWidthChange?.(t.config.id, width);
14979
15403
  }
15404
+ const eqLow = meta.trackEqLow?.[i2] ?? 0;
15405
+ if (t.trackEqLow !== eqLow) {
15406
+ t.trackEqLow = eqLow;
15407
+ options.onTrackEqLowChange?.(t.config.id, eqLow);
15408
+ }
15409
+ const eqMid = meta.trackEqMid?.[i2] ?? 0;
15410
+ if (t.trackEqMid !== eqMid) {
15411
+ t.trackEqMid = eqMid;
15412
+ options.onTrackEqMidChange?.(t.config.id, eqMid);
15413
+ }
15414
+ const eqHigh = meta.trackEqHigh?.[i2] ?? 0;
15415
+ if (t.trackEqHigh !== eqHigh) {
15416
+ t.trackEqHigh = eqHigh;
15417
+ options.onTrackEqHighChange?.(t.config.id, eqHigh);
15418
+ }
14980
15419
  });
14981
15420
  trackStates.forEach((t, i2) => {
14982
15421
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -14996,6 +15435,7 @@ var mountDAW = (target, options = {}) => {
14996
15435
  active.vocalOctave = 0;
14997
15436
  active.vocalVibrato = false;
14998
15437
  active.vocalReverb = 0;
15438
+ active.vocalDelay = 0;
14999
15439
  active.vocalGender = 50;
15000
15440
  active.vocalBreathiness = 50;
15001
15441
  } else {
@@ -15008,6 +15448,7 @@ var mountDAW = (target, options = {}) => {
15008
15448
  t.vocalOctave = 0;
15009
15449
  t.vocalVibrato = false;
15010
15450
  t.vocalReverb = 0;
15451
+ t.vocalDelay = 0;
15011
15452
  t.vocalGender = 50;
15012
15453
  t.vocalBreathiness = 50;
15013
15454
  }
@@ -15024,6 +15465,7 @@ var mountDAW = (target, options = {}) => {
15024
15465
  t.vocalOctave = lt.octave ?? 0;
15025
15466
  t.vocalVibrato = lt.vibrato ?? false;
15026
15467
  t.vocalReverb = lt.reverb ?? 0;
15468
+ t.vocalDelay = lt.delay ?? 0;
15027
15469
  t.vocalGender = lt.gender ?? 50;
15028
15470
  t.vocalBreathiness = lt.breathiness ?? 50;
15029
15471
  });
@@ -15106,6 +15548,7 @@ var mountDAW = (target, options = {}) => {
15106
15548
  active.vocalOctave = 0;
15107
15549
  active.vocalVibrato = false;
15108
15550
  active.vocalReverb = 0;
15551
+ active.vocalDelay = 0;
15109
15552
  active.vocalGender = 50;
15110
15553
  active.vocalBreathiness = 50;
15111
15554
  } else {
@@ -15120,6 +15563,7 @@ var mountDAW = (target, options = {}) => {
15120
15563
  t.vocalOctave = 0;
15121
15564
  t.vocalVibrato = false;
15122
15565
  t.vocalReverb = 0;
15566
+ t.vocalDelay = 0;
15123
15567
  t.vocalGender = 50;
15124
15568
  t.vocalBreathiness = 50;
15125
15569
  }
@@ -15170,6 +15614,7 @@ var mountDAW = (target, options = {}) => {
15170
15614
  bpm = value;
15171
15615
  refs.bpmInput.value = String(value);
15172
15616
  for (const t of trackStates) t.core.setTempo(value);
15617
+ options.onBpmChange?.(value);
15173
15618
  };
15174
15619
  let lastUndoTime = 0;
15175
15620
  const undo = () => {
@@ -15345,6 +15790,32 @@ var mountDAW = (target, options = {}) => {
15345
15790
  refs.reverbAmountInfoBtn.addEventListener("click", () => {
15346
15791
  showModal("\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC", MASTER_REVERB_INFO_HTML);
15347
15792
  });
15793
+ refs.reverbDecay.addEventListener("input", () => {
15794
+ const raw = Number.parseInt(refs.reverbDecay.value, 10) || 22;
15795
+ reverbDecay = Math.max(
15796
+ MIN_REVERB_DECAY_SEC,
15797
+ Math.min(MAX_REVERB_DECAY_SEC, raw / 10)
15798
+ );
15799
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
15800
+ options.onReverbDecayChange?.(reverbDecay);
15801
+ });
15802
+ refs.reverbPreDelay.addEventListener("input", () => {
15803
+ reverbPreDelay = Number.parseInt(refs.reverbPreDelay.value, 10) || 0;
15804
+ refs.reverbPreDelayLabel.textContent = `${reverbPreDelay}ms`;
15805
+ options.onReverbPreDelayChange?.(reverbPreDelay);
15806
+ });
15807
+ refs.delayAmount.addEventListener("input", () => {
15808
+ delayAmount = Number.parseInt(refs.delayAmount.value, 10) || 0;
15809
+ refs.delayAmountLabel.textContent = `${delayAmount}%`;
15810
+ options.onDelayChange?.(delayAmount);
15811
+ });
15812
+ refs.delayDivision.addEventListener("change", () => {
15813
+ delayDivision = refs.delayDivision.value;
15814
+ options.onDelayDivisionChange?.(delayDivision);
15815
+ });
15816
+ refs.delayAmountInfoBtn.addEventListener("click", () => {
15817
+ showModal("\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC", MASTER_DELAY_INFO_HTML);
15818
+ });
15348
15819
  refs.autoMasterInfoBtn.addEventListener("click", () => {
15349
15820
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15350
15821
  });
@@ -15353,14 +15824,39 @@ var mountDAW = (target, options = {}) => {
15353
15824
  refs.reverbAmount.value = "20";
15354
15825
  refs.reverbAmountLabel.textContent = "20%";
15355
15826
  options.onReverbChange?.(20);
15827
+ const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
15828
+ reverbDecay = clamp4(
15829
+ 3 - bpmT * (3 - 0.9),
15830
+ MIN_REVERB_DECAY_SEC,
15831
+ MAX_REVERB_DECAY_SEC
15832
+ );
15833
+ refs.reverbDecay.value = String(Math.round(reverbDecay * 10));
15834
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
15835
+ options.onReverbDecayChange?.(reverbDecay);
15836
+ reverbPreDelay = 20;
15837
+ refs.reverbPreDelay.value = "20";
15838
+ refs.reverbPreDelayLabel.textContent = "20ms";
15839
+ options.onReverbPreDelayChange?.(20);
15840
+ let mainVocalId = null;
15841
+ let mainVocalScore = -1;
15842
+ for (const t of trackStates) {
15843
+ if (!t.lyricModel) continue;
15844
+ const score = t.core.getNotes().reduce((sum, n) => sum + n.durationSteps, 0);
15845
+ if (score > mainVocalScore) {
15846
+ mainVocalScore = score;
15847
+ mainVocalId = t.config.id;
15848
+ }
15849
+ }
15356
15850
  for (const t of trackStates) {
15357
- t.trackCompression = 35;
15851
+ const isMainVocal = !!t.lyricModel && t.config.id === mainVocalId;
15852
+ const comp = !t.lyricModel ? 35 : isMainVocal ? 30 : 25;
15853
+ t.trackCompression = comp;
15358
15854
  t.trackWidth = 115;
15359
- options.onTrackCompressionChange?.(t.config.id, 35);
15855
+ options.onTrackCompressionChange?.(t.config.id, comp);
15360
15856
  options.onTrackWidthChange?.(t.config.id, 115);
15361
15857
  if (t.lyricModel) {
15362
15858
  t.vocalVibrato = true;
15363
- t.vocalReverb = 25;
15859
+ t.vocalReverb = isMainVocal ? 15 : 30;
15364
15860
  fireLyricsChange(t);
15365
15861
  }
15366
15862
  }
@@ -16242,7 +16738,7 @@ var mountDAW = (target, options = {}) => {
16242
16738
  currentPlayStep = step;
16243
16739
  playbackState = "paused";
16244
16740
  const canvas = getGridCanvas();
16245
- currentOffsetX = clamp3(
16741
+ currentOffsetX = clamp4(
16246
16742
  step * renderConfig.stepWidth - canvas.width * 0.5,
16247
16743
  0,
16248
16744
  getMaxOffsetX()
@@ -16288,11 +16784,11 @@ var mountDAW = (target, options = {}) => {
16288
16784
  getViewState,
16289
16785
  setViewState: (state) => {
16290
16786
  if (typeof state.zoomX === "number") {
16291
- zoomX = clamp3(state.zoomX, 25, 200);
16787
+ zoomX = clamp4(state.zoomX, 25, 200);
16292
16788
  applyZoomX();
16293
16789
  }
16294
16790
  if (typeof state.zoomY === "number") {
16295
- zoomY = clamp3(state.zoomY, 50, 200);
16791
+ zoomY = clamp4(state.zoomY, 50, 200);
16296
16792
  applyZoomY();
16297
16793
  }
16298
16794
  if (typeof state.decomposeChord === "boolean") {
@@ -16314,28 +16810,52 @@ var mountDAW = (target, options = {}) => {
16314
16810
  forcePauseAt,
16315
16811
  setLoading,
16316
16812
  setMasterVolume: (volume) => {
16317
- masterVolume = clamp3(volume, 0, 100);
16813
+ masterVolume = clamp4(volume, 0, 100);
16318
16814
  refs.masterVolume.value = String(masterVolume);
16319
16815
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
16320
16816
  options.singingVoices?.setVolume(masterVolume / 100);
16321
16817
  },
16322
16818
  setVolume: (volume) => {
16323
- masterVolume = clamp3(volume, 0, 100);
16819
+ masterVolume = clamp4(volume, 0, 100);
16324
16820
  refs.masterVolume.value = String(masterVolume);
16325
16821
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
16326
16822
  options.singingVoices?.setVolume(masterVolume / 100);
16327
16823
  },
16328
16824
  setDrumVolume: (volume) => {
16329
- drumVolume = clamp3(volume, 0, 100);
16825
+ drumVolume = clamp4(volume, 0, 100);
16330
16826
  refs.drumVolume.value = String(drumVolume);
16331
16827
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
16332
16828
  },
16333
16829
  setReverbAmount: (amount) => {
16334
- reverbAmount = clamp3(amount, 0, 100);
16830
+ reverbAmount = clamp4(amount, 0, 100);
16335
16831
  refs.reverbAmount.value = String(reverbAmount);
16336
16832
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
16337
16833
  options.onReverbChange?.(reverbAmount);
16338
16834
  },
16835
+ setReverbDecay: (seconds) => {
16836
+ reverbDecay = Math.max(
16837
+ MIN_REVERB_DECAY_SEC,
16838
+ Math.min(MAX_REVERB_DECAY_SEC, seconds)
16839
+ );
16840
+ refs.reverbDecay.value = String(Math.round(reverbDecay * 10));
16841
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
16842
+ options.onReverbDecayChange?.(reverbDecay);
16843
+ },
16844
+ setReverbPreDelay: (ms) => {
16845
+ reverbPreDelay = Math.max(
16846
+ MIN_REVERB_PREDELAY_MS,
16847
+ Math.min(MAX_REVERB_PREDELAY_MS, ms)
16848
+ );
16849
+ refs.reverbPreDelay.value = String(reverbPreDelay);
16850
+ refs.reverbPreDelayLabel.textContent = `${reverbPreDelay}ms`;
16851
+ options.onReverbPreDelayChange?.(reverbPreDelay);
16852
+ },
16853
+ setDelayAmount: (amount) => {
16854
+ delayAmount = clamp4(amount, 0, 100);
16855
+ refs.delayAmount.value = String(delayAmount);
16856
+ refs.delayAmountLabel.textContent = `${delayAmount}%`;
16857
+ options.onDelayChange?.(delayAmount);
16858
+ },
16339
16859
  applyPatch: (trackId, added, removed) => {
16340
16860
  const track = trackStates.find((t) => t.config.id === trackId);
16341
16861
  if (!track) return;
@@ -16377,6 +16897,7 @@ var mountDAW = (target, options = {}) => {
16377
16897
  t.vocalOctave = data.vocalOctave;
16378
16898
  t.vocalVibrato = data.vocalVibrato ?? false;
16379
16899
  t.vocalReverb = data.vocalReverb ?? 0;
16900
+ t.vocalDelay = data.vocalDelay ?? 0;
16380
16901
  t.vocalGender = data.vocalGender ?? 50;
16381
16902
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
16382
16903
  },
@@ -16497,6 +17018,7 @@ var playSingingMML = async (mml, options = {}) => {
16497
17018
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16498
17019
  vibrato: lt.vibrato,
16499
17020
  reverbSend: (lt.reverb ?? 0) / 100,
17021
+ delaySend: (lt.delay ?? 0) / 100,
16500
17022
  gender: (lt.gender ?? 50) / 100,
16501
17023
  breathiness: (lt.breathiness ?? 50) / 100,
16502
17024
  notes
@@ -17107,9 +17629,9 @@ var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
17107
17629
  URL.revokeObjectURL(url);
17108
17630
 
17109
17631
  // src/channel-strip.ts
17110
- var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17632
+ var clamp5 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17111
17633
  var compressionParams = (amount) => {
17112
- const t = clamp4(amount, 0, 100) / 100;
17634
+ const t = clamp5(amount, 0, 100) / 100;
17113
17635
  return {
17114
17636
  threshold: 0 + (-24 - 0) * t,
17115
17637
  ratio: 1 + (12 - 1) * t,
@@ -17118,8 +17640,29 @@ var compressionParams = (amount) => {
17118
17640
  release: 0.25 + (0.15 - 0.25) * t
17119
17641
  };
17120
17642
  };
17643
+ var EQ_LOW_FREQ = 200;
17644
+ var EQ_MID_FREQ = 1e3;
17645
+ var EQ_HIGH_FREQ = 5e3;
17646
+ var EQ_MID_Q = 1;
17647
+ var EQ_MAX_DB = 12;
17121
17648
  var createChannelStrip = (ctx, destination, options = {}) => {
17122
17649
  const input = ctx.createGain();
17650
+ const eqLow = ctx.createBiquadFilter();
17651
+ eqLow.type = "lowshelf";
17652
+ eqLow.frequency.value = EQ_LOW_FREQ;
17653
+ eqLow.gain.value = clamp5(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17654
+ const eqMid = ctx.createBiquadFilter();
17655
+ eqMid.type = "peaking";
17656
+ eqMid.frequency.value = EQ_MID_FREQ;
17657
+ eqMid.Q.value = EQ_MID_Q;
17658
+ eqMid.gain.value = clamp5(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17659
+ const eqHigh = ctx.createBiquadFilter();
17660
+ eqHigh.type = "highshelf";
17661
+ eqHigh.frequency.value = EQ_HIGH_FREQ;
17662
+ eqHigh.gain.value = clamp5(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17663
+ input.connect(eqLow);
17664
+ eqLow.connect(eqMid);
17665
+ eqMid.connect(eqHigh);
17123
17666
  const compressor = ctx.createDynamicsCompressor();
17124
17667
  const applyCompression = (amount) => {
17125
17668
  const p = compressionParams(amount);
@@ -17162,21 +17705,45 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17162
17705
  outR.connect(merger, 0, 1);
17163
17706
  const setWidth = (width) => {
17164
17707
  widthGain.gain.setTargetAtTime(
17165
- clamp4(width, 0, 200) / 100,
17708
+ clamp5(width, 0, 200) / 100,
17166
17709
  ctx.currentTime,
17167
17710
  0.02
17168
17711
  );
17169
17712
  };
17170
17713
  setWidth(options.width ?? 100);
17171
- input.connect(compressor);
17714
+ eqHigh.connect(compressor);
17172
17715
  compressor.connect(splitter);
17173
17716
  merger.connect(destination);
17174
17717
  return {
17175
17718
  input,
17719
+ setEqLow: (db) => {
17720
+ eqLow.gain.setTargetAtTime(
17721
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17722
+ ctx.currentTime,
17723
+ 0.02
17724
+ );
17725
+ },
17726
+ setEqMid: (db) => {
17727
+ eqMid.gain.setTargetAtTime(
17728
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17729
+ ctx.currentTime,
17730
+ 0.02
17731
+ );
17732
+ },
17733
+ setEqHigh: (db) => {
17734
+ eqHigh.gain.setTargetAtTime(
17735
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17736
+ ctx.currentTime,
17737
+ 0.02
17738
+ );
17739
+ },
17176
17740
  setCompression: applyCompression,
17177
17741
  setWidth,
17178
17742
  dispose: () => {
17179
17743
  input.disconnect();
17744
+ eqLow.disconnect();
17745
+ eqMid.disconnect();
17746
+ eqHigh.disconnect();
17180
17747
  compressor.disconnect();
17181
17748
  splitter.disconnect();
17182
17749
  mid.disconnect();
@@ -17247,25 +17814,6 @@ var createClipMeter = (ctx, source, options = {}) => {
17247
17814
  };
17248
17815
  };
17249
17816
 
17250
- // src/reverb.ts
17251
- var IMPULSE_DURATION_SEC = 2.2;
17252
- var IMPULSE_DECAY = 2.5;
17253
- var createReverbImpulse = (ctx) => {
17254
- const rate = ctx.sampleRate;
17255
- const length = Math.max(1, Math.floor(rate * IMPULSE_DURATION_SEC));
17256
- const impulse = ctx.createBuffer(2, length, rate);
17257
- for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
17258
- const data = impulse.getChannelData(ch);
17259
- for (let i2 = 0; i2 < length; i2++) {
17260
- const envelope = (1 - i2 / length) ** IMPULSE_DECAY;
17261
- data[i2] = (Math.random() * 2 - 1) * envelope;
17262
- }
17263
- }
17264
- return impulse;
17265
- };
17266
- var REVERB_MAX_WET = 0.6;
17267
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
17268
-
17269
17817
  // src/sf/SoundFont_drum.ts
17270
17818
  var touch = (map, key, ctor) => {
17271
17819
  if (!map.has(key)) map.set(key, new ctor());
@@ -17420,12 +17968,16 @@ var createDtmStudio = async (options = {}) => {
17420
17968
  const drumGain = audioCtx.createGain();
17421
17969
  drumGain.gain.value = options.drumVolume ?? 1;
17422
17970
  drumGain.connect(masterGain);
17971
+ let reverbDecaySec = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
17972
+ const reverbPreDelay = audioCtx.createDelay(MAX_REVERB_PREDELAY_MS / 1e3);
17973
+ reverbPreDelay.delayTime.value = (options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS) / 1e3;
17423
17974
  const reverbConvolver = audioCtx.createConvolver();
17424
- reverbConvolver.buffer = createReverbImpulse(audioCtx);
17975
+ reverbConvolver.buffer = createReverbImpulse(audioCtx, reverbDecaySec);
17425
17976
  reverbConvolver.normalize = true;
17426
17977
  const reverbWetGain = audioCtx.createGain();
17427
17978
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
17428
- masterGain.connect(reverbConvolver);
17979
+ masterGain.connect(reverbPreDelay);
17980
+ reverbPreDelay.connect(reverbConvolver);
17429
17981
  reverbConvolver.connect(reverbWetGain);
17430
17982
  const setReverbAmount = (amount) => {
17431
17983
  reverbWetGain.gain.setTargetAtTime(
@@ -17434,9 +17986,28 @@ var createDtmStudio = async (options = {}) => {
17434
17986
  0.02
17435
17987
  );
17436
17988
  };
17989
+ const setReverbDecay = (seconds) => {
17990
+ reverbDecaySec = Math.max(
17991
+ MIN_REVERB_DECAY_SEC,
17992
+ Math.min(MAX_REVERB_DECAY_SEC, seconds)
17993
+ );
17994
+ reverbConvolver.buffer = createReverbImpulse(audioCtx, reverbDecaySec);
17995
+ };
17996
+ const setReverbPreDelay = (ms) => {
17997
+ reverbPreDelay.delayTime.setTargetAtTime(
17998
+ Math.max(MIN_REVERB_PREDELAY_MS, Math.min(MAX_REVERB_PREDELAY_MS, ms)) / 1e3,
17999
+ audioCtx.currentTime,
18000
+ 0.02
18001
+ );
18002
+ };
17437
18003
  const finalMix = audioCtx.createGain();
17438
18004
  masterGain.connect(finalMix);
17439
18005
  reverbWetGain.connect(finalMix);
18006
+ const delayBus = createDelayBus(audioCtx, finalMix, {
18007
+ amount: options.delayAmount ?? 0,
18008
+ division: options.delayDivision ?? "8"
18009
+ });
18010
+ const setDelayAmount = (amount) => delayBus.setAmount(amount);
17440
18011
  const safetyLimiter = audioCtx.createDynamicsCompressor();
17441
18012
  safetyLimiter.threshold.value = -1;
17442
18013
  safetyLimiter.knee.value = 0;
@@ -17489,6 +18060,8 @@ var createDtmStudio = async (options = {}) => {
17489
18060
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
17490
18061
  // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
17491
18062
  reverbBus: reverbConvolver,
18063
+ // ボーカルトラック個別のディレイセンド(`e`トークン)先。
18064
+ delayBus: delayBus.input,
17492
18065
  // トラック単位チャンネルストリップ(コンプレッサー/ステレオワイド)の入口。
17493
18066
  // 楽器と同じ getChannelStrip キャッシュを共有するので、演奏トラックと歌詞トラックが
17494
18067
  // 同じ trackId を指していれば同じ処理が掛かる。
@@ -17769,8 +18342,20 @@ var createDtmStudio = async (options = {}) => {
17769
18342
  },
17770
18343
  reverbAmount: options.reverbAmount,
17771
18344
  onReverbChange: setReverbAmount,
18345
+ reverbDecay: options.reverbDecay,
18346
+ onReverbDecayChange: setReverbDecay,
18347
+ reverbPreDelay: options.reverbPreDelay,
18348
+ onReverbPreDelayChange: setReverbPreDelay,
18349
+ delayAmount: options.delayAmount,
18350
+ onDelayChange: setDelayAmount,
18351
+ delayDivision: options.delayDivision,
18352
+ onDelayDivisionChange: (division) => delayBus.setDivision(division),
18353
+ onBpmChange: (bpm) => delayBus.setBpm(bpm),
17772
18354
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
17773
18355
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18356
+ onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
18357
+ onTrackEqMidChange: (trackId, db) => getChannelStrip(trackId).setEqMid(db),
18358
+ onTrackEqHighChange: (trackId, db) => getChannelStrip(trackId).setEqHigh(db),
17774
18359
  clipMeter,
17775
18360
  ...dawOverrides,
17776
18361
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
@@ -18307,6 +18892,7 @@ var createDtmStudio = async (options = {}) => {
18307
18892
  mountedChordPlayers.length = 0;
18308
18893
  mountedEditors.length = 0;
18309
18894
  clipMeter.dispose();
18895
+ delayBus.dispose();
18310
18896
  for (const strip of channelStrips.values()) strip.dispose();
18311
18897
  channelStrips.clear();
18312
18898
  void audioCtx.close();
@@ -18348,6 +18934,9 @@ var createDtmStudio = async (options = {}) => {
18348
18934
  setMasterVolume,
18349
18935
  setVolume: setMasterVolume,
18350
18936
  setReverbAmount,
18937
+ setReverbDecay,
18938
+ setReverbPreDelay,
18939
+ setDelayAmount,
18351
18940
  dispose
18352
18941
  };
18353
18942
  };