@onjmin/dtm 0.1.85 → 0.1.87

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,12 @@ 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;
2192
+ let octaveDouble = false;
2191
2193
  const modelMatch = rest.match(
2192
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrgh]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2194
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2193
2195
  );
2194
2196
  let model = "";
2195
2197
  const metaTokens = [];
@@ -2258,6 +2260,20 @@ var parseLyrics = (mml) => {
2258
2260
  rest = rest.substring(hMatch[0].length).trim();
2259
2261
  continue;
2260
2262
  }
2263
+ const eMatch = rest.match(/^e(\d+)/i);
2264
+ if (eMatch) {
2265
+ delay = clamp(Number.parseInt(eMatch[1], 10), 0, 100);
2266
+ metaTokens.push(eMatch[0]);
2267
+ rest = rest.substring(eMatch[0].length).trim();
2268
+ continue;
2269
+ }
2270
+ const wMatch = rest.match(/^w([01])/i);
2271
+ if (wMatch) {
2272
+ octaveDouble = wMatch[1] === "1";
2273
+ metaTokens.push(wMatch[0]);
2274
+ rest = rest.substring(wMatch[0].length).trim();
2275
+ continue;
2276
+ }
2261
2277
  break;
2262
2278
  }
2263
2279
  const lyricLines = [rest];
@@ -2274,8 +2290,10 @@ var parseLyrics = (mml) => {
2274
2290
  octave,
2275
2291
  vibrato,
2276
2292
  reverb,
2293
+ delay,
2277
2294
  gender,
2278
2295
  breathiness,
2296
+ octaveDouble,
2279
2297
  syllables,
2280
2298
  metaText: metaTokens.join(" "),
2281
2299
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2361,7 +2379,7 @@ var FORMANTS = {
2361
2379
  N: [250, 1e3]
2362
2380
  };
2363
2381
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
2364
- var createKlattVoice = (ctx, destination, reverbBus) => {
2382
+ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
2365
2383
  const active = /* @__PURE__ */ new Set();
2366
2384
  const voice = (syllable, e) => {
2367
2385
  const t0 = ctx.currentTime + e.when;
@@ -2380,11 +2398,17 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2380
2398
  panner.connect(dest);
2381
2399
  out = panner;
2382
2400
  }
2383
- let sendGain = null;
2401
+ let reverbSendGain = null;
2384
2402
  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);
2403
+ reverbSendGain = ctx.createGain();
2404
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2405
+ panner.connect(reverbSendGain).connect(reverbBus);
2406
+ }
2407
+ let delaySendGain = null;
2408
+ if (delayBus && e.delaySend && e.delaySend > 0 && panner) {
2409
+ delaySendGain = ctx.createGain();
2410
+ delaySendGain.gain.value = Math.max(0, Math.min(1, e.delaySend));
2411
+ panner.connect(delaySendGain).connect(delayBus);
2388
2412
  }
2389
2413
  const osc = ctx.createOscillator();
2390
2414
  osc.type = "sawtooth";
@@ -2441,7 +2465,8 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2441
2465
  active.delete(osc);
2442
2466
  osc.disconnect();
2443
2467
  panner?.disconnect();
2444
- sendGain?.disconnect();
2468
+ reverbSendGain?.disconnect();
2469
+ delaySendGain?.disconnect();
2445
2470
  };
2446
2471
  };
2447
2472
  voice.stopAll = () => {
@@ -2757,7 +2782,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2757
2782
  return p;
2758
2783
  };
2759
2784
  const LEADCAP_S = 0.09;
2760
- const schedule = (r, t0, peak, pan, reverbSend = 0, destOverride) => {
2785
+ const schedule = (r, t0, peak, pan, reverbSend = 0, delaySend = 0, destOverride) => {
2761
2786
  const dest = destOverride ?? destination;
2762
2787
  let out = dest;
2763
2788
  let panner = null;
@@ -2767,11 +2792,17 @@ var createKoeVoice = async (ctx, destination, options) => {
2767
2792
  panner.connect(dest);
2768
2793
  out = panner;
2769
2794
  }
2770
- let sendGain = null;
2795
+ let reverbSendGain = null;
2771
2796
  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);
2797
+ reverbSendGain = ctx.createGain();
2798
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2799
+ panner.connect(reverbSendGain).connect(options.reverbBus);
2800
+ }
2801
+ let delaySendGain = null;
2802
+ if (options.delayBus && delaySend > 0 && panner) {
2803
+ delaySendGain = ctx.createGain();
2804
+ delaySendGain.gain.value = Math.max(0, Math.min(1, delaySend));
2805
+ panner.connect(delaySendGain).connect(options.delayBus);
2775
2806
  }
2776
2807
  const src = ctx.createBufferSource();
2777
2808
  src.buffer = r.audio;
@@ -2798,7 +2829,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2798
2829
  src.disconnect();
2799
2830
  env.disconnect();
2800
2831
  panner?.disconnect();
2801
- sendGain?.disconnect();
2832
+ reverbSendGain?.disconnect();
2833
+ delaySendGain?.disconnect();
2802
2834
  };
2803
2835
  };
2804
2836
  const model = (syllable, e) => {
@@ -2817,7 +2849,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2817
2849
  const pan = e.pan ?? 0;
2818
2850
  const durationMs = Math.max(60, e.duration * 1e3);
2819
2851
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2820
- if (r) schedule(r, t0, peak, pan, e.reverbSend, e.destination);
2852
+ if (r)
2853
+ schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
2821
2854
  });
2822
2855
  };
2823
2856
  model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato, expr) => {
@@ -2835,9 +2868,9 @@ var createKoeVoice = async (ctx, destination, options) => {
2835
2868
  const r = await renderInto(alias, pitch, dMs, vib, expr);
2836
2869
  return r ? keyOf(alias, pitch, dMs, vib, expr) : null;
2837
2870
  };
2838
- model.scheduleCached = (key, t0, peak, pan, reverbSend, dest) => {
2871
+ model.scheduleCached = (key, t0, peak, pan, reverbSend, delaySend, dest) => {
2839
2872
  const r = renderCache.get(key);
2840
- if (r) schedule(r, t0, peak, pan, reverbSend, dest);
2873
+ if (r) schedule(r, t0, peak, pan, reverbSend, delaySend, dest);
2841
2874
  };
2842
2875
  model.stopAll = () => {
2843
2876
  for (const src of active) {
@@ -2857,6 +2890,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2857
2890
  var PREWARM_NOTES = 3;
2858
2891
  var STREAM_LOOKAHEAD_SEC = 1.5;
2859
2892
  var STREAM_POLL_MS = 100;
2893
+ var OCTAVE_DOUBLE_SEMITONES = -12;
2894
+ var OCTAVE_DOUBLE_PEAK_SCALE = 0.6;
2860
2895
  var FALLBACK_MODEL = "klatt";
2861
2896
  var createSingingVoices = (ctx, destination, options = {}) => {
2862
2897
  const catalog = {};
@@ -2867,8 +2902,12 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2867
2902
  let streamSession = 0;
2868
2903
  const masterGain = ctx.createGain();
2869
2904
  masterGain.connect(destination);
2905
+ let masterVolumeScalar = 1;
2870
2906
  const loaded = /* @__PURE__ */ new Map([
2871
- [FALLBACK_MODEL, createKlattVoice(ctx, masterGain, options.reverbBus)]
2907
+ [
2908
+ FALLBACK_MODEL,
2909
+ createKlattVoice(ctx, masterGain, options.reverbBus, options.delayBus)
2910
+ ]
2872
2911
  ]);
2873
2912
  const loading = /* @__PURE__ */ new Map();
2874
2913
  const load2 = (model) => {
@@ -2889,7 +2928,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2889
2928
  worldlineScriptUrl: options.worldlineScriptUrl,
2890
2929
  lightweight: options.lightweight,
2891
2930
  voiceWorkerUrl: options.voiceWorkerUrl,
2892
- reverbBus: options.reverbBus
2931
+ reverbBus: options.reverbBus,
2932
+ delayBus: options.delayBus
2893
2933
  })
2894
2934
  ))().then((v) => {
2895
2935
  loaded.set(m, v);
@@ -2938,7 +2978,24 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2938
2978
  forEachSungNote(track, (note, prevVowel) => {
2939
2979
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2940
2980
  n++;
2941
- tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
2981
+ tasks.push({
2982
+ model: m,
2983
+ note,
2984
+ prevVowel,
2985
+ pitch: note.pitch,
2986
+ vibrato: track.vibrato,
2987
+ expr
2988
+ });
2989
+ if (track.octaveDouble) {
2990
+ tasks.push({
2991
+ model: m,
2992
+ note,
2993
+ prevVowel,
2994
+ pitch: note.pitch + OCTAVE_DOUBLE_SEMITONES,
2995
+ vibrato: track.vibrato,
2996
+ expr
2997
+ });
2998
+ }
2942
2999
  });
2943
3000
  }
2944
3001
  const total = tasks.length;
@@ -2952,7 +3009,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2952
3009
  await (task.model.renderToCache?.(
2953
3010
  task.note.syllable,
2954
3011
  task.prevVowel,
2955
- task.note.pitch,
3012
+ task.pitch,
2956
3013
  task.note.durationSec * 1e3,
2957
3014
  task.vibrato,
2958
3015
  task.expr
@@ -2992,53 +3049,70 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2992
3049
  }
2993
3050
  if (opts?.isAudible && !opts.isAudible(track)) continue;
2994
3051
  const t0 = anchorTime + startSec;
2995
- if (model.renderToCache && model.scheduleCached) {
2996
- const renderToCache = model.renderToCache;
2997
- const scheduleCached = model.scheduleCached;
2998
- void (async () => {
2999
- const key = await renderToCache(
3000
- note.syllable,
3001
- prevVowel,
3002
- note.pitch,
3003
- note.durationSec * 1e3,
3004
- track.vibrato,
3005
- { gender: track.gender, breathiness: track.breathiness }
3006
- );
3007
- if (session !== streamSession) return;
3008
- if (key) {
3009
- const delay = ctx.currentTime - t0;
3010
- if (delay < 0.05) {
3011
- scheduleCached(
3012
- key,
3013
- t0,
3014
- peak,
3015
- track.pan,
3016
- track.reverbSend,
3017
- options.getTrackDestination?.(track.id ?? "")
3018
- );
3019
- opts?.onScheduled?.(track, note, t0);
3020
- } else {
3021
- console.warn(
3022
- `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3023
- );
3024
- opts?.onLateSkip?.(note, delay);
3052
+ const dispatchNote = (pitch, peakScale) => {
3053
+ if (model.renderToCache && model.scheduleCached) {
3054
+ const renderToCache = model.renderToCache;
3055
+ const scheduleCached = model.scheduleCached;
3056
+ void (async () => {
3057
+ const key = await renderToCache(
3058
+ note.syllable,
3059
+ prevVowel,
3060
+ pitch,
3061
+ note.durationSec * 1e3,
3062
+ track.vibrato,
3063
+ { gender: track.gender, breathiness: track.breathiness }
3064
+ );
3065
+ if (session !== streamSession) return;
3066
+ if (key) {
3067
+ const delay = ctx.currentTime - t0;
3068
+ if (delay < 0.05) {
3069
+ const dest = options.getTrackDestination?.(track.id ?? "");
3070
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3071
+ scheduleCached(
3072
+ key,
3073
+ t0,
3074
+ effPeak,
3075
+ track.pan,
3076
+ track.reverbSend,
3077
+ track.delaySend,
3078
+ dest
3079
+ );
3080
+ opts?.onScheduled?.(track, note, t0);
3081
+ } else {
3082
+ console.warn(
3083
+ `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3084
+ );
3085
+ opts?.onLateSkip?.(note, delay);
3086
+ }
3025
3087
  }
3026
- }
3027
- })();
3028
- } else {
3029
- const when = t0 - ctx.currentTime;
3030
- model(note.syllable, {
3031
- trackId: track.id ?? "",
3032
- pitch: note.pitch,
3033
- velocity: 100,
3034
- volume: peak,
3035
- when,
3036
- duration: note.durationSec,
3037
- pan: track.pan,
3038
- reverbSend: track.reverbSend,
3039
- destination: options.getTrackDestination?.(track.id ?? "")
3040
- });
3041
- opts?.onScheduled?.(track, note, t0);
3088
+ })();
3089
+ } else {
3090
+ const when = t0 - ctx.currentTime;
3091
+ const dest = options.getTrackDestination?.(track.id ?? "");
3092
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3093
+ model(note.syllable, {
3094
+ trackId: track.id ?? "",
3095
+ pitch,
3096
+ velocity: 100,
3097
+ volume: effPeak,
3098
+ when,
3099
+ duration: note.durationSec,
3100
+ pan: track.pan,
3101
+ reverbSend: track.reverbSend,
3102
+ delaySend: track.delaySend,
3103
+ destination: dest
3104
+ });
3105
+ opts?.onScheduled?.(track, note, t0);
3106
+ }
3107
+ };
3108
+ dispatchNote(note.pitch, 1);
3109
+ if (track.octaveDouble) {
3110
+ dispatchNote(
3111
+ note.pitch + OCTAVE_DOUBLE_SEMITONES,
3112
+ OCTAVE_DOUBLE_PEAK_SCALE
3113
+ );
3114
+ }
3115
+ if (!(model.renderToCache && model.scheduleCached)) {
3042
3116
  await new Promise((resolve) => setTimeout(resolve, 0));
3043
3117
  }
3044
3118
  }
@@ -3061,7 +3135,9 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3061
3135
  for (const v of loaded.values()) v.reset?.();
3062
3136
  };
3063
3137
  const setVolume = (gain) => {
3064
- masterGain.gain.value = Math.max(0, gain);
3138
+ const g = Math.max(0, gain);
3139
+ masterGain.gain.value = g;
3140
+ masterVolumeScalar = g;
3065
3141
  };
3066
3142
  return {
3067
3143
  loadModels,
@@ -3095,10 +3171,13 @@ var PITCH_MAP = {
3095
3171
  b: 11
3096
3172
  };
3097
3173
  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;
3174
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3099
3175
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3100
3176
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3101
3177
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3178
+ var TRACK_EQLOW_DIRECTIVE = /#t(\d+)eqlo=(-?\d+)/gi;
3179
+ var TRACK_EQMID_DIRECTIVE = /#t(\d+)eqmid=(-?\d+)/gi;
3180
+ var TRACK_EQHIGH_DIRECTIVE = /#t(\d+)eqhi=(-?\d+)/gi;
3102
3181
  var parseMmlMeta = (mml) => {
3103
3182
  const meta = {};
3104
3183
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -3115,6 +3194,23 @@ var parseMmlMeta = (mml) => {
3115
3194
  } else if (key === "reverb") {
3116
3195
  const rv = Number.parseInt(m[2], 10);
3117
3196
  if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3197
+ } else if (key === "reverbdecay") {
3198
+ const rd = Number.parseInt(m[2], 10);
3199
+ if (!Number.isNaN(rd)) meta.reverbDecay = clamp2(rd, 3, 40);
3200
+ } else if (key === "reverbpredelay") {
3201
+ const rp = Number.parseInt(m[2], 10);
3202
+ if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp2(rp, 0, 150);
3203
+ } else if (key === "delay") {
3204
+ const dv = Number.parseInt(m[2], 10);
3205
+ if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3206
+ } else if (key === "delaydiv") {
3207
+ if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3208
+ } else if (key === "fadein") {
3209
+ const fi = Number.parseInt(m[2], 10);
3210
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3211
+ } else if (key === "fadeout") {
3212
+ const fo = Number.parseInt(m[2], 10);
3213
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3118
3214
  } else if (key === "mode") {
3119
3215
  if (m[2] === "simple" || m[2] === "advanced") {
3120
3216
  meta.mode = m[2];
@@ -3145,9 +3241,33 @@ var parseMmlMeta = (mml) => {
3145
3241
  meta.trackWidth[idx] = val;
3146
3242
  }
3147
3243
  }
3244
+ for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3245
+ const idx = Number.parseInt(m[1], 10);
3246
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3247
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3248
+ meta.trackEqLow ??= {};
3249
+ meta.trackEqLow[idx] = val;
3250
+ }
3251
+ }
3252
+ for (const m of mml.matchAll(TRACK_EQMID_DIRECTIVE)) {
3253
+ const idx = Number.parseInt(m[1], 10);
3254
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3255
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3256
+ meta.trackEqMid ??= {};
3257
+ meta.trackEqMid[idx] = val;
3258
+ }
3259
+ }
3260
+ for (const m of mml.matchAll(TRACK_EQHIGH_DIRECTIVE)) {
3261
+ const idx = Number.parseInt(m[1], 10);
3262
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3263
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3264
+ meta.trackEqHigh ??= {};
3265
+ meta.trackEqHigh[idx] = val;
3266
+ }
3267
+ }
3148
3268
  return meta;
3149
3269
  };
3150
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "");
3270
+ 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
3271
  var formatMmlMeta = (meta, space = "") => {
3152
3272
  const parts = [];
3153
3273
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3158,6 +3278,18 @@ var formatMmlMeta = (meta, space = "") => {
3158
3278
  parts.push(`#drumvolume=${meta.drumVolume}`);
3159
3279
  if (meta.reverb !== void 0 && meta.reverb !== 0)
3160
3280
  parts.push(`#reverb=${meta.reverb}`);
3281
+ if (meta.reverbDecay !== void 0 && meta.reverbDecay !== 22)
3282
+ parts.push(`#reverbdecay=${meta.reverbDecay}`);
3283
+ if (meta.reverbPreDelay !== void 0 && meta.reverbPreDelay !== 0)
3284
+ parts.push(`#reverbpredelay=${meta.reverbPreDelay}`);
3285
+ if (meta.delay !== void 0 && meta.delay !== 0)
3286
+ parts.push(`#delay=${meta.delay}`);
3287
+ if (meta.delayDivision && meta.delayDivision !== "8")
3288
+ parts.push(`#delaydiv=${meta.delayDivision}`);
3289
+ if (meta.fadeIn !== void 0 && meta.fadeIn !== 0)
3290
+ parts.push(`#fadein=${meta.fadeIn}`);
3291
+ if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
3292
+ parts.push(`#fadeout=${meta.fadeOut}`);
3161
3293
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3162
3294
  if (meta.trackInstruments) {
3163
3295
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
@@ -3174,6 +3306,21 @@ var formatMmlMeta = (meta, space = "") => {
3174
3306
  if (val !== 100) parts.push(`#t${idx}width=${val}`);
3175
3307
  }
3176
3308
  }
3309
+ if (meta.trackEqLow) {
3310
+ for (const [idx, val] of Object.entries(meta.trackEqLow)) {
3311
+ if (val !== 0) parts.push(`#t${idx}eqlo=${val}`);
3312
+ }
3313
+ }
3314
+ if (meta.trackEqMid) {
3315
+ for (const [idx, val] of Object.entries(meta.trackEqMid)) {
3316
+ if (val !== 0) parts.push(`#t${idx}eqmid=${val}`);
3317
+ }
3318
+ }
3319
+ if (meta.trackEqHigh) {
3320
+ for (const [idx, val] of Object.entries(meta.trackEqHigh)) {
3321
+ if (val !== 0) parts.push(`#t${idx}eqhi=${val}`);
3322
+ }
3323
+ }
3177
3324
  return parts.join(space);
3178
3325
  };
3179
3326
  var parseMML = (mml, options = {}) => {
@@ -9009,8 +9156,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9009
9156
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
9010
9157
  vibrato: lt.vibrato,
9011
9158
  reverbSend: (lt.reverb ?? 0) / 100,
9159
+ delaySend: (lt.delay ?? 0) / 100,
9012
9160
  gender: (lt.gender ?? 50) / 100,
9013
9161
  breathiness: (lt.breathiness ?? 50) / 100,
9162
+ octaveDouble: lt.octaveDouble,
9014
9163
  notes
9015
9164
  };
9016
9165
  });
@@ -10688,6 +10837,71 @@ var mountChordPlayer = (target, chords, options = {}) => {
10688
10837
  };
10689
10838
  };
10690
10839
 
10840
+ // src/delay.ts
10841
+ var DELAY_DIVISIONS = [
10842
+ { value: "4", label: "4\u5206", beats: 1 },
10843
+ { value: "8", label: "8\u5206", beats: 0.5 },
10844
+ { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
10845
+ { value: "16", label: "16\u5206", beats: 0.25 }
10846
+ ];
10847
+ var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
10848
+ var divisionToSeconds = (division, bpm) => {
10849
+ const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
10850
+ const safeBpm = bpm > 0 ? bpm : 120;
10851
+ return 60 / safeBpm * beats;
10852
+ };
10853
+ var DELAY_MAX_WET = 0.45;
10854
+ var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
10855
+ var FEEDBACK_GAIN = 0.3;
10856
+ var MAX_DELAY_SEC = 2;
10857
+ var createDelayBus = (ctx, destination, options = {}) => {
10858
+ const input = ctx.createGain();
10859
+ const delayNode = ctx.createDelay(MAX_DELAY_SEC);
10860
+ const feedback = ctx.createGain();
10861
+ feedback.gain.value = FEEDBACK_GAIN;
10862
+ const wetGain = ctx.createGain();
10863
+ wetGain.gain.value = delayAmountToGain(options.amount ?? 0);
10864
+ input.connect(delayNode);
10865
+ delayNode.connect(feedback);
10866
+ feedback.connect(delayNode);
10867
+ delayNode.connect(wetGain);
10868
+ wetGain.connect(destination);
10869
+ let bpm = options.bpm ?? 120;
10870
+ let division = options.division ?? "8";
10871
+ const applyTime = () => {
10872
+ delayNode.delayTime.setTargetAtTime(
10873
+ divisionToSeconds(division, bpm),
10874
+ ctx.currentTime,
10875
+ 0.05
10876
+ );
10877
+ };
10878
+ applyTime();
10879
+ return {
10880
+ input,
10881
+ setAmount: (amount) => {
10882
+ wetGain.gain.setTargetAtTime(
10883
+ delayAmountToGain(amount),
10884
+ ctx.currentTime,
10885
+ 0.02
10886
+ );
10887
+ },
10888
+ setDivision: (d) => {
10889
+ division = d;
10890
+ applyTime();
10891
+ },
10892
+ setBpm: (b) => {
10893
+ bpm = b;
10894
+ applyTime();
10895
+ },
10896
+ dispose: () => {
10897
+ input.disconnect();
10898
+ delayNode.disconnect();
10899
+ feedback.disconnect();
10900
+ wetGain.disconnect();
10901
+ }
10902
+ };
10903
+ };
10904
+
10691
10905
  // src/daw-ui.ts
10692
10906
  var q = (root, sel) => root.querySelector(sel);
10693
10907
  var buildUI = (target, options) => {
@@ -10804,6 +11018,36 @@ var buildUI = (target, options) => {
10804
11018
  <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10805
11019
  <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
11020
  </div>
11021
+ <div class="dtm-row">
11022
+ <span class="dtm-label">Decay</span>
11023
+ <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">
11024
+ <span class="dtm-label" data-dtm="reverb-decay-label">2.2s</span>
11025
+ </div>
11026
+ <div class="dtm-row">
11027
+ <span class="dtm-label">Pre Delay</span>
11028
+ <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">
11029
+ <span class="dtm-label" data-dtm="reverb-predelay-label">0ms</span>
11030
+ </div>
11031
+ <div class="dtm-row">
11032
+ <span class="dtm-label">\u30C7\u30A3\u30EC\u30A4</span>
11033
+ <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">
11034
+ <span class="dtm-label" data-dtm="delay-amount-label">0%</span>
11035
+ <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">
11036
+ ${DELAY_DIVISIONS.map((d) => `<option value="${d.value}">${d.label}</option>`).join("")}
11037
+ </select>
11038
+ <button class="dtm-infobtn" data-dtm="delay-amount-info" title="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11039
+ </div>
11040
+ <div class="dtm-row">
11041
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3</span>
11042
+ <input type="range" class="dtm-range dtm-grow" data-dtm="fade-in" value="0" min="0" max="10" step="0.5" aria-label="\u66F2\u982D\u306E\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u9577\uFF08\u79D2\uFF09">
11043
+ <span class="dtm-label" data-dtm="fade-in-label">0.0s</span>
11044
+ <button class="dtm-infobtn" data-dtm="fade-info" title="\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11045
+ </div>
11046
+ <div class="dtm-row">
11047
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8</span>
11048
+ <input type="range" class="dtm-range dtm-grow" data-dtm="fade-out" value="0" min="0" max="10" step="0.5" aria-label="\u66F2\u5C3E\u306E\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u9577\uFF08\u79D2\uFF09">
11049
+ <span class="dtm-label" data-dtm="fade-out-label">0.0s</span>
11050
+ </div>
10807
11051
  </div>
10808
11052
  </details>
10809
11053
 
@@ -10888,6 +11132,25 @@ var buildUI = (target, options) => {
10888
11132
  </select>
10889
11133
  <button class="dtm-btn dtm-btn--primary" data-dtm="shift-apply">\u9069\u7528</button>
10890
11134
  </div>
11135
+ <div class="dtm-row">
11136
+ <span class="dtm-label">\u79FB\u8ABF</span>
11137
+ <select class="dtm-select" data-dtm="transpose-select" aria-label="\u79FB\u8ABF\u3059\u308B\u534A\u97F3\u6570">
11138
+ <option value="-12">-1oct</option>
11139
+ <option value="-7">-5th</option>
11140
+ <option value="-5">-4th</option>
11141
+ <option value="-3">-3\u534A\u97F3</option>
11142
+ <option value="-2">-2\u534A\u97F3</option>
11143
+ <option value="-1">-1\u534A\u97F3</option>
11144
+ <option value="1">+1\u534A\u97F3</option>
11145
+ <option value="2">+2\u534A\u97F3</option>
11146
+ <option value="3">+3\u534A\u97F3</option>
11147
+ <option value="5">+4th</option>
11148
+ <option value="7">+5th</option>
11149
+ <option value="12">+1oct</option>
11150
+ </select>
11151
+ <button class="dtm-btn dtm-btn--primary" data-dtm="transpose-apply">\u9069\u7528</button>
11152
+ <button class="dtm-infobtn" data-dtm="transpose-info" title="\u79FB\u8ABF\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11153
+ </div>
10891
11154
  <div class="dtm-row">
10892
11155
  <button class="dtm-btn dtm-btn--danger" data-dtm="macro-clear">\u5168\u6D88\u53BB</button>
10893
11156
  <button class="dtm-btn dtm-btn--accent" data-dtm="macro-random">\u30E9\u30F3\u30C0\u30E0\u914D\u7F6E</button>
@@ -11002,6 +11265,19 @@ var buildUI = (target, options) => {
11002
11265
  reverbAmount: sel("reverb-amount"),
11003
11266
  reverbAmountLabel: sel("reverb-amount-label"),
11004
11267
  reverbAmountInfoBtn: sel("reverb-amount-info"),
11268
+ reverbDecay: sel("reverb-decay"),
11269
+ reverbDecayLabel: sel("reverb-decay-label"),
11270
+ reverbPreDelay: sel("reverb-predelay"),
11271
+ reverbPreDelayLabel: sel("reverb-predelay-label"),
11272
+ delayAmount: sel("delay-amount"),
11273
+ delayAmountLabel: sel("delay-amount-label"),
11274
+ delayAmountInfoBtn: sel("delay-amount-info"),
11275
+ delayDivision: sel("delay-division"),
11276
+ fadeIn: sel("fade-in"),
11277
+ fadeInLabel: sel("fade-in-label"),
11278
+ fadeOut: sel("fade-out"),
11279
+ fadeOutLabel: sel("fade-out-label"),
11280
+ fadeInfoBtn: sel("fade-info"),
11005
11281
  autoMasterBtn: sel("auto-master"),
11006
11282
  autoMasterInfoBtn: sel("auto-master-info"),
11007
11283
  trackTabs: sel("track-tabs"),
@@ -11022,6 +11298,9 @@ var buildUI = (target, options) => {
11022
11298
  applyActiveOnly: sel("apply-active-only"),
11023
11299
  shiftSelect: sel("shift-select"),
11024
11300
  shiftApplyBtn: sel("shift-apply"),
11301
+ transposeSelect: sel("transpose-select"),
11302
+ transposeApplyBtn: sel("transpose-apply"),
11303
+ transposeInfoBtn: sel("transpose-info"),
11025
11304
  macroClear: sel("macro-clear"),
11026
11305
  macroRandom: sel("macro-random"),
11027
11306
  macroHarmonic: sel("macro-harmonic"),
@@ -11276,6 +11555,18 @@ var shiftNotes = (cores, shiftSteps) => {
11276
11555
  }
11277
11556
  }
11278
11557
  };
11558
+ var transposeNotes = (cores, semitones) => {
11559
+ if (semitones === 0) return;
11560
+ for (const core of cores) {
11561
+ const notes = [...core.getNotes()];
11562
+ for (const note of notes) {
11563
+ const newPitch = Math.max(0, Math.min(127, note.pitch + semitones));
11564
+ if (newPitch !== note.pitch) {
11565
+ core.moveNote(note.id, note.startStep, newPitch);
11566
+ }
11567
+ }
11568
+ }
11569
+ };
11279
11570
 
11280
11571
  // src/midi-io.ts
11281
11572
  var STEPS_PER_BEAT3 = 48;
@@ -12635,6 +12926,38 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
12635
12926
  return chordNotes / notes.length >= threshold;
12636
12927
  };
12637
12928
 
12929
+ // src/reverb.ts
12930
+ var DEFAULT_REVERB_DECAY_SEC = 2.2;
12931
+ var MIN_REVERB_DECAY_SEC = 0.3;
12932
+ var MAX_REVERB_DECAY_SEC = 4;
12933
+ var IMPULSE_DECAY_CURVE = 2.5;
12934
+ var DEFAULT_REVERB_PREDELAY_MS = 0;
12935
+ var MIN_REVERB_PREDELAY_MS = 0;
12936
+ var MAX_REVERB_PREDELAY_MS = 150;
12937
+ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
12938
+ const rate = ctx.sampleRate;
12939
+ const length = Math.max(
12940
+ 1,
12941
+ Math.floor(
12942
+ rate * Math.max(
12943
+ MIN_REVERB_DECAY_SEC,
12944
+ Math.min(MAX_REVERB_DECAY_SEC, decaySec)
12945
+ )
12946
+ )
12947
+ );
12948
+ const impulse = ctx.createBuffer(2, length, rate);
12949
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
12950
+ const data = impulse.getChannelData(ch);
12951
+ for (let i2 = 0; i2 < length; i2++) {
12952
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
12953
+ data[i2] = (Math.random() * 2 - 1) * envelope;
12954
+ }
12955
+ }
12956
+ return impulse;
12957
+ };
12958
+ var REVERB_MAX_WET = 0.6;
12959
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
12960
+
12638
12961
  // src/daw.ts
12639
12962
  var CHORD_INFO_HTML2 = `
12640
12963
  <div class="dtm-modal-body-content">
@@ -12676,6 +12999,16 @@ var VIBRATO_INFO_HTML = `
12676
12999
  <p>\u30D4\u30C3\u30C1\u3092\u63FA\u3089\u3059\u52B9\u679C\u306A\u306E\u3067\u3001\u58F0\u8CEA\u305D\u306E\u3082\u306E\u3092\u5909\u3048\u308B\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306F\u72EC\u7ACB\u3057\u3066\u7D44\u307F\u5408\u308F\u305B\u3089\u308C\u307E\u3059\u3002\u901F\u3055\u30FB\u6DF1\u3055\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\uFF08\u6B4C\u3068\u3057\u3066\u7834\u7DBB\u3057\u306B\u304F\u3044\u63A7\u3048\u3081\u306A\u91CF\u306B\u56FA\u5B9A\uFF09\u3002\u66F2\u3084\u7B87\u6240\u3054\u3068\u306B\u639B\u3051\u305F\u3044/\u639B\u3051\u305F\u304F\u306A\u3044\u304C\u3042\u308B\u5834\u5408\u306F\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u6B4C\u8A5E\u3092\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002</p>
12677
13000
  </div>
12678
13001
  `;
13002
+ var OCTAVE_DOUBLE_INFO_HTML = `
13003
+ <div class="dtm-modal-body-content">
13004
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13005
+ <p>ON\u306B\u3059\u308B\u3068\u3001\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u5404\u97F3\u7BC0\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\uFF08\u63A7\u3048\u3081\u306A\u97F3\u91CF\uFF09\u3067\u540C\u6642\u306B\u91CD\u306D\u3066\u767A\u97F3\u3057\u307E\u3059\u3002\u58F0\u306B\u539A\u307F\u30FB\u30D1\u30EF\u30FC\u3092\u8DB3\u3059\u300C\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EA\u30F3\u30B0\u300D\u3068\u3044\u3046\u5B9A\u756A\u306E\u30DC\u30FC\u30AB\u30EB\u52A0\u5DE5\u3067\u3059\u3002</p>
13006
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13007
+ <p>\u30B5\u30D3\u306E\u6C7A\u3081\u306E\u30D5\u30EC\u30FC\u30BA\u3001\u30ED\u30DC\u30C3\u30C8\u7684/\u30C0\u30FC\u30AF\u306A\u8CEA\u611F\u306E\u6F14\u51FA\u3001\u30E6\u30CB\u30BE\u30F3\u30CF\u30FC\u30E2\u30CB\u30FC\u306A\u3069\u3067\u3088\u304F\u4F7F\u308F\u308C\u307E\u3059\u3002\u66F2\u5168\u4F53\u306B\u639B\u3051\u3063\u3071\u306A\u3057\u306B\u3059\u308B\u3068\u5E38\u306B\u304F\u3069\u3044\u5370\u8C61\u306B\u306A\u308A\u3084\u3059\u3044\u306E\u3067\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u4F7F\u3044\u305F\u3044\u7B87\u6240\u3060\u3051\u306B\u7D5E\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
13008
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13009
+ <p>\u91CD\u306D\u308B\u58F0\u306B\u3082\u540C\u3058\u30D3\u30D6\u30E9\u30FC\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9/\u30EA\u30D0\u30FC\u30D6\u9001\u308A/\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u8A2D\u5B9A\u304C\u305D\u306E\u307E\u307E\u9069\u7528\u3055\u308C\u307E\u3059\u3002\u97F3\u91CF\u6BD4\u30FB\u97F3\u7A0B\u5DEE\uFF081\u30AA\u30AF\u30BF\u30FC\u30D6\u56FA\u5B9A\uFF09\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\u3002\u5408\u6210\u304C\u5358\u7D14\u306B2\u500D\u8D70\u308B\u305F\u3081\u3001\u97F3\u6570\u306E\u591A\u3044\u30C8\u30E9\u30C3\u30AF\u3067\u306F\u3084\u3084\u91CD\u304F\u306A\u308A\u307E\u3059\u3002</p>
13010
+ </div>
13011
+ `;
12679
13012
  var GENDER_INFO_HTML = `
12680
13013
  <div class="dtm-modal-body-content">
12681
13014
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -12706,14 +13039,66 @@ var LYRIC_REVERB_INFO_HTML = `
12706
13039
  <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
13040
  </div>
12708
13041
  `;
12709
- var MASTER_REVERB_INFO_HTML = `
13042
+ var LYRIC_DELAY_INFO_HTML = `
12710
13043
  <div class="dtm-modal-body-content">
12711
13044
  <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>
13045
+ <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>
13046
+ <h4>\u30DE\u30B9\u30BF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u300D\u3064\u307E\u307F\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
13047
+ <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>
13048
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13049
+ <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>
13050
+ </div>
13051
+ `;
13052
+ var MASTER_REVERB_INFO_HTML = `
13053
+ <div class="dtm-modal-body-content">
13054
+ <h4>\u30EA\u30D0\u30FC\u30D6\uFF08Mix\uFF09</h4>
13055
+ <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>
13056
+ <h4>Decay\uFF08\u6B8B\u97FF\u306E\u9577\u3055\uFF09</h4>
13057
+ <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>
13058
+ <h4>Pre Delay\uFF08\u539F\u97F3\u3068\u30EA\u30D0\u30FC\u30D6\u306E\u9593\u9694\uFF09</h4>
13059
+ <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
13060
  <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>
13061
+ <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>
13062
+ </div>
13063
+ `;
13064
+ var MASTER_DELAY_INFO_HTML = `
13065
+ <div class="dtm-modal-body-content">
13066
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13067
+ <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>
13068
+ <h4>\u30EA\u30D0\u30FC\u30D6\u3068\u306E\u9055\u3044</h4>
13069
+ <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>
13070
+ <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2</h4>
13071
+ <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>
13072
+ </div>
13073
+ `;
13074
+ var FADE_INFO_HTML = `
13075
+ <div class="dtm-modal-body-content">
13076
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13077
+ <p>\u66F2\u306E\u982D\u3067\u97F3\u91CF0%\u304B\u3089\u5F90\u3005\u306B\u4E0A\u3052\u308B\u300C\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u300D\u3001\u66F2\u306E\u7D42\u308F\u308A\u3067\u97F3\u91CF\u30920%\u307E\u3067\u5F90\u3005\u306B\u4E0B\u3052\u308B\u300C\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u300D\u3092\u8A2D\u5B9A\u3057\u307E\u3059\u30020\u79D2\u3067\u30D5\u30A7\u30FC\u30C9\u306A\u3057\u3067\u3059\u3002</p>
13078
+ <h4>\u3044\u3064\u52B9\u304F\u304B</h4>
13079
+ <p>\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u306F\u66F2\u306E\u5148\u982D\uFF080\u5C0F\u7BC0\u76EE\uFF09\u304B\u3089\u518D\u751F\u3057\u305F\u3068\u304D\u3060\u3051\u639B\u304B\u308A\u307E\u3059\u3002\u9014\u4E2D\u306E\u5C0F\u7BC0\u304B\u3089\u518D\u751F\u30FB\u30B7\u30FC\u30AF\u3057\u305F\u5834\u5408\u306F\u639B\u304B\u308A\u307E\u305B\u3093\u3002\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u306F\u3001\u4ECA\u30CE\u30FC\u30C8\u304C\u7F6E\u304B\u308C\u3066\u3044\u308B\u7BC4\u56F2\u306E\u7D42\u7AEF\u306B\u5411\u3051\u3066\u81EA\u52D5\u7684\u306B\u639B\u304B\u308A\u307E\u3059\uFF08\u30CE\u30FC\u30C8\u3092\u8DB3\u305B\u3070\u7D42\u7AEF\u3082\u4F38\u3073\u307E\u3059\uFF09\u3002</p>
13080
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13081
+ <p>\u7DE8\u96C6\u4E2D\u306F\u307B\u3068\u3093\u3069\u610F\u8B58\u3057\u307E\u305B\u3093\u304C\u3001\u66F8\u304D\u51FA\u3057\u3066\u4EBA\u306B\u8074\u304B\u305B\u308B\u30FB\u914D\u4FE1\u3059\u308B\u6BB5\u306B\u306A\u308B\u3068\u3001\u66F2\u306E\u51FA\u5165\u308A\u304C\u5510\u7A81\u3060\u3068\u7D20\u4EBA\u3063\u307D\u304F\u805E\u3053\u3048\u304C\u3061\u3067\u3059\u3002\u6570\u79D2\u306E\u30D5\u30A7\u30FC\u30C9\u3092\u5165\u308C\u308B\u3060\u3051\u3067\u4ED5\u4E0A\u304C\u308A\u306E\u5370\u8C61\u304C\u5909\u308F\u308A\u307E\u3059\u3002</p>
13082
+ </div>
13083
+ `;
13084
+ var TRANSPOSE_INFO_HTML = `
13085
+ <div class="dtm-modal-body-content">
13086
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13087
+ <p>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30CE\u30FC\u30C8\u3092\u9078\u3093\u3060\u534A\u97F3\u6570\u3060\u3051\u4E00\u62EC\u3067\u79FB\u8ABF\u3057\u307E\u3059\u3002\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306E\u6B4C\u58F0\u3082\u30CE\u30FC\u30C8\u306E\u30D4\u30C3\u30C1\u306B\u8FFD\u5F93\u3059\u308B\u305F\u3081\u3001\u30DC\u30FC\u30AB\u30EB\u3082\u4E00\u7DD2\u306B\u79FB\u8ABF\u3055\u308C\u307E\u3059\u3002</p>
13088
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13089
+ <p>\u300C\u6B4C\u3063\u3066\u307F\u305F\u3089\u97F3\u57DF\u304C\u9AD8\u3059\u304E\u305F/\u4F4E\u3059\u304E\u305F\u300D\u3068\u3044\u3046\u3068\u304D\u306E\u8ABF\u6574\u5B9A\u756A\u3067\u3059\u3002\u30DC\u30FC\u30AB\u30EB\u306E\u58F0\u57DF\u306B\u5408\u308F\u305B\u3066\u66F2\u5168\u4F53\u306E\u30AD\u30FC\u3092\u4E0A\u3052\u4E0B\u3052\u3067\u304D\u307E\u3059\u3002</p>
13090
+ <h4>\u6CE8\u610F</h4>
13091
+ <p>\u30CE\u30FC\u30C8\u30C7\u30FC\u30BF\u3092\u76F4\u63A5\u66F8\u304D\u63DB\u3048\u308B\u64CD\u4F5C\u3067\u3059\uFF08\u5143\u306B\u623B\u3059\u306B\u306F\u300C\u5143\u306B\u623B\u3059\u300D\u30DC\u30BF\u30F3\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\uFF09\u3002MIDI\u30CE\u30FC\u30C8\u7BC4\u56F2\uFF080-127\uFF09\u3092\u8D85\u3048\u308B\u97F3\u306F\u7BC4\u56F2\u5185\u306B\u30AF\u30E9\u30F3\u30D7\u3055\u308C\u307E\u3059\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>
12717
13102
  </div>
12718
13103
  `;
12719
13104
  var TRACK_COMPRESSION_INFO_HTML = `
@@ -12739,14 +13124,22 @@ var TRACK_WIDTH_INFO_HTML = `
12739
13124
  var AUTO_MASTER_INFO_HTML = `
12740
13125
  <div class="dtm-modal-body-content">
12741
13126
  <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>
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>
12743
13128
  <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>
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>
12746
13133
  <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>
13134
+ <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON</li>
13135
+ </ul>
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>
12748
13141
  </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>
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>
12750
13143
  </div>
12751
13144
  `;
12752
13145
  var MIDI_INFO_HTML = `
@@ -12982,7 +13375,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
12982
13375
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
12983
13376
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
12984
13377
  };
12985
- 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);
12986
13379
  var normalizeInstrumentName = (name) => {
12987
13380
  if (!name) return "";
12988
13381
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13022,6 +13415,21 @@ var mountDAW = (target, options = {}) => {
13022
13415
  refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
13023
13416
  refs.reverbAmount.value = String(options.reverbAmount ?? 0);
13024
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";
13429
+ refs.fadeIn.value = String(options.fadeInSec ?? 0);
13430
+ refs.fadeInLabel.textContent = `${(options.fadeInSec ?? 0).toFixed(1)}s`;
13431
+ refs.fadeOut.value = String(options.fadeOutSec ?? 0);
13432
+ refs.fadeOutLabel.textContent = `${(options.fadeOutSec ?? 0).toFixed(1)}s`;
13025
13433
  refs.drumVolume.value = String(options.drumVolume ?? 80);
13026
13434
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
13027
13435
  const renderConfig = {
@@ -13038,8 +13446,18 @@ var mountDAW = (target, options = {}) => {
13038
13446
  let masterVolume = options.masterVolume ?? 50;
13039
13447
  options.singingVoices?.setVolume(masterVolume / 100);
13040
13448
  let reverbAmount = options.reverbAmount ?? 0;
13449
+ let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
13450
+ let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13451
+ let delayAmount = options.delayAmount ?? 0;
13452
+ let delayDivision = options.delayDivision ?? "8";
13453
+ let fadeInSec = options.fadeInSec ?? 0;
13454
+ let fadeOutSec = options.fadeOutSec ?? 0;
13041
13455
  let unsubscribeClip;
13042
13456
  options.onReverbChange?.(reverbAmount);
13457
+ options.onReverbDecayChange?.(reverbDecay);
13458
+ options.onReverbPreDelayChange?.(reverbPreDelay);
13459
+ options.onDelayChange?.(delayAmount);
13460
+ options.onDelayDivisionChange?.(delayDivision);
13043
13461
  let drumVolume = options.drumVolume ?? 80;
13044
13462
  let currentDrumPattern = refs.drumSelect.value;
13045
13463
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
@@ -13120,8 +13538,10 @@ var mountDAW = (target, options = {}) => {
13120
13538
  vocalOctave: t.vocalOctave,
13121
13539
  vocalVibrato: t.vocalVibrato,
13122
13540
  vocalReverb: t.vocalReverb,
13541
+ vocalDelay: t.vocalDelay,
13123
13542
  vocalGender: t.vocalGender,
13124
- vocalBreathiness: t.vocalBreathiness
13543
+ vocalBreathiness: t.vocalBreathiness,
13544
+ vocalOctaveDouble: t.vocalOctaveDouble
13125
13545
  };
13126
13546
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13127
13547
  lyricsDebounceTimer = setTimeout(() => {
@@ -13183,11 +13603,16 @@ var mountDAW = (target, options = {}) => {
13183
13603
  vocalOctave: 0,
13184
13604
  vocalVibrato: false,
13185
13605
  vocalReverb: 0,
13606
+ vocalDelay: 0,
13186
13607
  vocalGender: 50,
13187
13608
  vocalBreathiness: 50,
13609
+ vocalOctaveDouble: false,
13188
13610
  trackInstrument: "",
13189
13611
  trackCompression: 0,
13190
- trackWidth: 100
13612
+ trackWidth: 100,
13613
+ trackEqLow: 0,
13614
+ trackEqMid: 0,
13615
+ trackEqHigh: 0
13191
13616
  };
13192
13617
  });
13193
13618
  };
@@ -13208,8 +13633,10 @@ var mountDAW = (target, options = {}) => {
13208
13633
  octave: t.vocalOctave,
13209
13634
  vibrato: t.vocalVibrato,
13210
13635
  reverb: t.vocalReverb,
13636
+ delay: t.vocalDelay,
13211
13637
  gender: t.vocalGender,
13212
13638
  breathiness: t.vocalBreathiness,
13639
+ octaveDouble: t.vocalOctaveDouble,
13213
13640
  syllables
13214
13641
  });
13215
13642
  });
@@ -13217,16 +13644,18 @@ var mountDAW = (target, options = {}) => {
13217
13644
  };
13218
13645
  const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
13219
13646
  let showModal;
13220
- const getMaxNoteStep = () => {
13647
+ const getSongEndStepExact = () => {
13221
13648
  let maxEndStep = 0;
13222
13649
  for (const t of trackStates) {
13223
13650
  for (const n of t.core.getNotes()) {
13224
13651
  const end = n.startStep + n.durationSteps;
13225
- if (end > maxEndStep) {
13226
- maxEndStep = end;
13227
- }
13652
+ if (end > maxEndStep) maxEndStep = end;
13228
13653
  }
13229
13654
  }
13655
+ return maxEndStep;
13656
+ };
13657
+ const getMaxNoteStep = () => {
13658
+ const maxEndStep = getSongEndStepExact();
13230
13659
  if (maxEndStep === 0) {
13231
13660
  return renderConfig.stepsPerBar * 4;
13232
13661
  }
@@ -13330,7 +13759,7 @@ var mountDAW = (target, options = {}) => {
13330
13759
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13331
13760
  const ratio = currentOffsetX / maxOffsetX;
13332
13761
  refs.hScrollThumb.style.width = `${thumbW}px`;
13333
- refs.hScrollThumb.style.left = `${clamp3(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13762
+ refs.hScrollThumb.style.left = `${clamp4(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13334
13763
  }
13335
13764
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13336
13765
  const sbH = refs.vScroll.clientHeight;
@@ -13400,9 +13829,9 @@ var mountDAW = (target, options = {}) => {
13400
13829
  if (maxOffsetX <= 0) return;
13401
13830
  const rect = refs.hScroll.getBoundingClientRect();
13402
13831
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13403
- const x2 = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13832
+ const x2 = clamp4(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13404
13833
  const ratio = x2 / (rect.width - thumbW);
13405
- currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
13834
+ currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
13406
13835
  setDrawOffset(currentOffsetX, currentOffsetY);
13407
13836
  redrawAll();
13408
13837
  };
@@ -13411,9 +13840,9 @@ var mountDAW = (target, options = {}) => {
13411
13840
  if (maxOffset <= 0) return;
13412
13841
  const rect = refs.vScroll.getBoundingClientRect();
13413
13842
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
13414
- const y = clamp3(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13843
+ const y = clamp4(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13415
13844
  const ratio = y / (rect.height - thumbH);
13416
- currentOffsetY = clamp3(ratio * maxOffset, 0, maxOffset);
13845
+ currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
13417
13846
  setDrawOffset(currentOffsetX, currentOffsetY);
13418
13847
  redrawAll();
13419
13848
  };
@@ -13460,8 +13889,8 @@ var mountDAW = (target, options = {}) => {
13460
13889
  if (dx !== 0 || dy !== 0) {
13461
13890
  const maxOffsetX = getMaxOffsetX();
13462
13891
  const maxOffsetY = getMaxOffsetY();
13463
- currentOffsetX = clamp3(currentOffsetX + dx, 0, maxOffsetX);
13464
- currentOffsetY = clamp3(currentOffsetY + dy, 0, maxOffsetY);
13892
+ currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
13893
+ currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
13465
13894
  setDrawOffset(currentOffsetX, currentOffsetY);
13466
13895
  onPointerMove(lastMoveEvent);
13467
13896
  }
@@ -13851,12 +14280,12 @@ var mountDAW = (target, options = {}) => {
13851
14280
  "wheel",
13852
14281
  (event) => {
13853
14282
  event.preventDefault();
13854
- currentOffsetY = clamp3(
14283
+ currentOffsetY = clamp4(
13855
14284
  currentOffsetY + event.deltaY,
13856
14285
  0,
13857
14286
  getMaxOffsetY()
13858
14287
  );
13859
- currentOffsetX = clamp3(
14288
+ currentOffsetX = clamp4(
13860
14289
  currentOffsetX + event.deltaX,
13861
14290
  0,
13862
14291
  getMaxOffsetX()
@@ -13895,7 +14324,7 @@ var mountDAW = (target, options = {}) => {
13895
14324
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
13896
14325
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
13897
14326
  refs.zoomXLabel.textContent = `${zoomX}%`;
13898
- currentOffsetX = clamp3(
14327
+ currentOffsetX = clamp4(
13899
14328
  centerStep * renderConfig.stepWidth - canvas.width / 2,
13900
14329
  0,
13901
14330
  getMaxOffsetX()
@@ -13908,7 +14337,7 @@ var mountDAW = (target, options = {}) => {
13908
14337
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
13909
14338
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
13910
14339
  refs.zoomYLabel.textContent = `${zoomY}%`;
13911
- currentOffsetY = clamp3(
14340
+ currentOffsetY = clamp4(
13912
14341
  centerKey * renderConfig.keyHeight - canvas.height / 2,
13913
14342
  0,
13914
14343
  getMaxOffsetY()
@@ -13958,7 +14387,7 @@ var mountDAW = (target, options = {}) => {
13958
14387
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
13959
14388
  if (currentPlayStep > threshold) {
13960
14389
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
13961
- currentOffsetX = clamp3(
14390
+ currentOffsetX = clamp4(
13962
14391
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
13963
14392
  0,
13964
14393
  getMaxOffsetX()
@@ -14014,8 +14443,10 @@ var mountDAW = (target, options = {}) => {
14014
14443
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
14015
14444
  vibrato: lt.vibrato,
14016
14445
  reverbSend: (lt.reverb ?? 0) / 100,
14446
+ delaySend: (lt.delay ?? 0) / 100,
14017
14447
  gender: (lt.gender ?? 50) / 100,
14018
14448
  breathiness: (lt.breathiness ?? 50) / 100,
14449
+ octaveDouble: lt.octaveDouble,
14019
14450
  notes
14020
14451
  };
14021
14452
  }) : [];
@@ -14043,7 +14474,7 @@ var mountDAW = (target, options = {}) => {
14043
14474
  }
14044
14475
  if (playbackState !== "paused") {
14045
14476
  const canvas = getGridCanvas();
14046
- currentOffsetX = clamp3(
14477
+ currentOffsetX = clamp4(
14047
14478
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14048
14479
  0,
14049
14480
  getMaxOffsetX()
@@ -14052,6 +14483,29 @@ var mountDAW = (target, options = {}) => {
14052
14483
  }
14053
14484
  playbackState = "playing";
14054
14485
  sequencer.start(fromStep);
14486
+ {
14487
+ const anchor = sequencer.getStartTime();
14488
+ const secondsPerStepFade = 60 / bpm / 48;
14489
+ const params = {};
14490
+ if (fadeInSec > 0 && fromStep === 0) {
14491
+ params.fadeInStartAt = anchor;
14492
+ params.fadeInEndAt = anchor + fadeInSec;
14493
+ }
14494
+ if (fadeOutSec > 0) {
14495
+ const endStep = getSongEndStepExact();
14496
+ const totalDurationSec = (endStep - fromStep) * secondsPerStepFade;
14497
+ if (totalDurationSec > 0) {
14498
+ const fadeOutEndAt = anchor + totalDurationSec;
14499
+ const earliestStart = params.fadeInEndAt ?? anchor;
14500
+ params.fadeOutEndAt = fadeOutEndAt;
14501
+ params.fadeOutStartAt = Math.max(
14502
+ fadeOutEndAt - fadeOutSec,
14503
+ earliestStart
14504
+ );
14505
+ }
14506
+ }
14507
+ options.onScheduleFade?.(params);
14508
+ }
14055
14509
  if (streaming && voices) {
14056
14510
  voices.startStream(streamTracks, sequencer.getStartTime(), {
14057
14511
  isAudible: (t) => !isSolo || t.id === activeTrackId,
@@ -14069,6 +14523,7 @@ var mountDAW = (target, options = {}) => {
14069
14523
  sequencer.stop();
14070
14524
  options.singingVoices?.stopStream();
14071
14525
  clearSoundTimers();
14526
+ options.onScheduleFade?.(null);
14072
14527
  playbackState = "paused";
14073
14528
  updateTransport();
14074
14529
  };
@@ -14082,6 +14537,7 @@ var mountDAW = (target, options = {}) => {
14082
14537
  sequencer.stop();
14083
14538
  options.singingVoices?.stopStream();
14084
14539
  clearSoundTimers();
14540
+ options.onScheduleFade?.(null);
14085
14541
  playbackState = "stopped";
14086
14542
  currentPlayStep = 0;
14087
14543
  updateTransport();
@@ -14123,7 +14579,23 @@ var mountDAW = (target, options = {}) => {
14123
14579
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14124
14580
  </div>
14125
14581
  <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>
14582
+ <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14583
+ <div class="dtm-row">
14584
+ <span class="dtm-label">EQ\u4F4E\u57DF</span>
14585
+ <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">
14586
+ <span class="dtm-label" data-dtm="track-eq-low-label"></span>
14587
+ <button class="dtm-infobtn" data-dtm="track-eq-info" title="EQ\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14588
+ </div>
14589
+ <div class="dtm-row">
14590
+ <span class="dtm-label">EQ\u4E2D\u57DF</span>
14591
+ <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">
14592
+ <span class="dtm-label" data-dtm="track-eq-mid-label"></span>
14593
+ </div>
14594
+ <div class="dtm-row">
14595
+ <span class="dtm-label">EQ\u9AD8\u57DF</span>
14596
+ <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">
14597
+ <span class="dtm-label" data-dtm="track-eq-high-label"></span>
14598
+ </div>
14127
14599
  <div class="dtm-row">
14128
14600
  <span class="dtm-label">\u97F3\u5727\u5F37\u5316</span>
14129
14601
  <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 +14625,52 @@ var mountDAW = (target, options = {}) => {
14153
14625
  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
14626
  };
14155
14627
  syncVelocityDisabled();
14628
+ const trackEqLowInput = refs.trackBody.querySelector(
14629
+ '[data-dtm="track-eq-low"]'
14630
+ );
14631
+ const trackEqLowLabel = refs.trackBody.querySelector(
14632
+ '[data-dtm="track-eq-low-label"]'
14633
+ );
14634
+ const trackEqMidInput = refs.trackBody.querySelector(
14635
+ '[data-dtm="track-eq-mid"]'
14636
+ );
14637
+ const trackEqMidLabel = refs.trackBody.querySelector(
14638
+ '[data-dtm="track-eq-mid-label"]'
14639
+ );
14640
+ const trackEqHighInput = refs.trackBody.querySelector(
14641
+ '[data-dtm="track-eq-high"]'
14642
+ );
14643
+ const trackEqHighLabel = refs.trackBody.querySelector(
14644
+ '[data-dtm="track-eq-high-label"]'
14645
+ );
14646
+ const trackEqInfo = refs.trackBody.querySelector(
14647
+ '[data-dtm="track-eq-info"]'
14648
+ );
14649
+ const fmtDb = (db) => db > 0 ? `+${db}dB` : `${db}dB`;
14650
+ trackEqLowInput.value = String(active.trackEqLow);
14651
+ trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
14652
+ trackEqMidInput.value = String(active.trackEqMid);
14653
+ trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
14654
+ trackEqHighInput.value = String(active.trackEqHigh);
14655
+ trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
14656
+ trackEqLowInput.addEventListener("input", () => {
14657
+ active.trackEqLow = Number.parseInt(trackEqLowInput.value, 10);
14658
+ trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
14659
+ options.onTrackEqLowChange?.(active.config.id, active.trackEqLow);
14660
+ });
14661
+ trackEqMidInput.addEventListener("input", () => {
14662
+ active.trackEqMid = Number.parseInt(trackEqMidInput.value, 10);
14663
+ trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
14664
+ options.onTrackEqMidChange?.(active.config.id, active.trackEqMid);
14665
+ });
14666
+ trackEqHighInput.addEventListener("input", () => {
14667
+ active.trackEqHigh = Number.parseInt(trackEqHighInput.value, 10);
14668
+ trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
14669
+ options.onTrackEqHighChange?.(active.config.id, active.trackEqHigh);
14670
+ });
14671
+ trackEqInfo.addEventListener("click", () => {
14672
+ showModal("EQ\uFF08\u30A4\u30B3\u30E9\u30A4\u30B6\u30FC\uFF09\u306E\u89E3\u8AAC", TRACK_EQ_INFO_HTML);
14673
+ });
14156
14674
  const trackCompInput = refs.trackBody.querySelector(
14157
14675
  '[data-dtm="track-comp"]'
14158
14676
  );
@@ -14305,6 +14823,12 @@ var mountDAW = (target, options = {}) => {
14305
14823
  <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14306
14824
  <button class="dtm-infobtn" data-dtm="lyric-reverb-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14307
14825
  </div>
14826
+ <div class="dtm-row">
14827
+ <span class="dtm-label">\u30C7\u30A3\u30EC\u30A4\u9001\u308A</span>
14828
+ <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">
14829
+ <span class="dtm-label" data-dtm="lyric-delay-label"></span>
14830
+ <button class="dtm-infobtn" data-dtm="lyric-delay-info" title="\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14831
+ </div>
14308
14832
  <div class="dtm-row">
14309
14833
  <span class="dtm-label">\u30B8\u30A7\u30F3\u30C0\u30FC</span>
14310
14834
  <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">
@@ -14323,6 +14847,12 @@ var mountDAW = (target, options = {}) => {
14323
14847
  </span>
14324
14848
  <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14325
14849
  </div>
14850
+ <div class="dtm-row">
14851
+ <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14852
+ <input type="checkbox" data-dtm="lyric-octave-double" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB">\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB
14853
+ </span>
14854
+ <button class="dtm-infobtn" data-dtm="lyric-octave-double-info" title="\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14855
+ </div>
14326
14856
  </details>
14327
14857
  <textarea class="dtm-textarea" data-dtm="lyric-input" rows="2" placeholder="\u3072\u3089\u304C\u306A\u30FB\u30AB\u30BF\u30AB\u30CA\u3067\u6B4C\u8A5E\uFF08\u4F8B: \u3069\u308C\u307F\u3075\u3041\u305D\u3089\u3057\u3069\uFF09"></textarea>
14328
14858
  </div>`;
@@ -14369,6 +14899,18 @@ var mountDAW = (target, options = {}) => {
14369
14899
  lyricReverbInfo.addEventListener("click", () => {
14370
14900
  showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_REVERB_INFO_HTML);
14371
14901
  });
14902
+ const lyricDelay = lyricDiv.querySelector(
14903
+ '[data-dtm="lyric-delay"]'
14904
+ );
14905
+ const lyricDelayLabel = lyricDiv.querySelector(
14906
+ '[data-dtm="lyric-delay-label"]'
14907
+ );
14908
+ const lyricDelayInfo = lyricDiv.querySelector(
14909
+ '[data-dtm="lyric-delay-info"]'
14910
+ );
14911
+ lyricDelayInfo.addEventListener("click", () => {
14912
+ showModal("\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_DELAY_INFO_HTML);
14913
+ });
14372
14914
  const lyricGender = lyricDiv.querySelector(
14373
14915
  '[data-dtm="lyric-gender"]'
14374
14916
  );
@@ -14402,6 +14944,15 @@ var mountDAW = (target, options = {}) => {
14402
14944
  lyricVibratoInfo.addEventListener("click", () => {
14403
14945
  showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14404
14946
  });
14947
+ const lyricOctaveDouble = lyricDiv.querySelector(
14948
+ '[data-dtm="lyric-octave-double"]'
14949
+ );
14950
+ const lyricOctaveDoubleInfo = lyricDiv.querySelector(
14951
+ '[data-dtm="lyric-octave-double-info"]'
14952
+ );
14953
+ lyricOctaveDoubleInfo.addEventListener("click", () => {
14954
+ showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u89E3\u8AAC", OCTAVE_DOUBLE_INFO_HTML);
14955
+ });
14405
14956
  const lyricTerms = lyricDiv.querySelector(
14406
14957
  '[data-dtm="lyric-terms"]'
14407
14958
  );
@@ -14465,11 +15016,14 @@ var mountDAW = (target, options = {}) => {
14465
15016
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14466
15017
  lyricReverb.value = String(active.vocalReverb);
14467
15018
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
15019
+ lyricDelay.value = String(active.vocalDelay);
15020
+ lyricDelayLabel.textContent = `${active.vocalDelay}%`;
14468
15021
  lyricGender.value = String(active.vocalGender);
14469
15022
  lyricGenderLabel.textContent = `${active.vocalGender}`;
14470
15023
  lyricBreathiness.value = String(active.vocalBreathiness);
14471
15024
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14472
15025
  lyricVibrato.checked = active.vocalVibrato;
15026
+ lyricOctaveDouble.checked = active.vocalOctaveDouble;
14473
15027
  const updateLyricCount = () => {
14474
15028
  const n = normalizeLyrics(lyricInput.value).length;
14475
15029
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -14591,6 +15145,10 @@ var mountDAW = (target, options = {}) => {
14591
15145
  active.vocalVibrato = lyricVibrato.checked;
14592
15146
  fireLyricsChange(active);
14593
15147
  });
15148
+ lyricOctaveDouble.addEventListener("change", () => {
15149
+ active.vocalOctaveDouble = lyricOctaveDouble.checked;
15150
+ fireLyricsChange(active);
15151
+ });
14594
15152
  lyricInput.addEventListener("input", () => {
14595
15153
  active.lyrics = lyricInput.value;
14596
15154
  updateLyricCount();
@@ -14619,6 +15177,11 @@ var mountDAW = (target, options = {}) => {
14619
15177
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14620
15178
  fireLyricsChange(active);
14621
15179
  });
15180
+ lyricDelay.addEventListener("input", () => {
15181
+ active.vocalDelay = Number.parseInt(lyricDelay.value, 10);
15182
+ lyricDelayLabel.textContent = `${active.vocalDelay}%`;
15183
+ fireLyricsChange(active);
15184
+ });
14622
15185
  lyricGender.addEventListener("input", () => {
14623
15186
  active.vocalGender = Number.parseInt(lyricGender.value, 10);
14624
15187
  lyricGenderLabel.textContent = `${active.vocalGender}`;
@@ -14714,15 +15277,24 @@ var mountDAW = (target, options = {}) => {
14714
15277
  const trackInstrumentsForMeta = {};
14715
15278
  const trackCompressionForMeta = {};
14716
15279
  const trackWidthForMeta = {};
15280
+ const trackEqLowForMeta = {};
15281
+ const trackEqMidForMeta = {};
15282
+ const trackEqHighForMeta = {};
14717
15283
  trackStates.forEach((t, i2) => {
14718
15284
  if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14719
15285
  if (t.trackCompression !== 0)
14720
15286
  trackCompressionForMeta[i2] = t.trackCompression;
14721
15287
  if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
15288
+ if (t.trackEqLow !== 0) trackEqLowForMeta[i2] = t.trackEqLow;
15289
+ if (t.trackEqMid !== 0) trackEqMidForMeta[i2] = t.trackEqMid;
15290
+ if (t.trackEqHigh !== 0) trackEqHighForMeta[i2] = t.trackEqHigh;
14722
15291
  });
14723
15292
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14724
15293
  const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
14725
15294
  const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
15295
+ const trackEqLowMeta = Object.keys(trackEqLowForMeta).length > 0 ? trackEqLowForMeta : void 0;
15296
+ const trackEqMidMeta = Object.keys(trackEqMidForMeta).length > 0 ? trackEqMidForMeta : void 0;
15297
+ const trackEqHighMeta = Object.keys(trackEqHighForMeta).length > 0 ? trackEqHighForMeta : void 0;
14726
15298
  const metaLineFull = formatMmlMeta(
14727
15299
  {
14728
15300
  instrument: currentInstrument || void 0,
@@ -14730,10 +15302,19 @@ var mountDAW = (target, options = {}) => {
14730
15302
  volume: masterVolume,
14731
15303
  drumVolume,
14732
15304
  reverb: reverbAmount,
15305
+ reverbDecay: Math.round(reverbDecay * 10),
15306
+ reverbPreDelay,
15307
+ delay: delayAmount,
15308
+ delayDivision,
15309
+ fadeIn: Math.round(fadeInSec * 10),
15310
+ fadeOut: Math.round(fadeOutSec * 10),
14733
15311
  mode,
14734
15312
  trackInstruments: trackInstMeta,
14735
15313
  trackCompression: trackCompMeta,
14736
- trackWidth: trackWidthMeta
15314
+ trackWidth: trackWidthMeta,
15315
+ trackEqLow: trackEqLowMeta,
15316
+ trackEqMid: trackEqMidMeta,
15317
+ trackEqHigh: trackEqHighMeta
14737
15318
  },
14738
15319
  " "
14739
15320
  );
@@ -14744,10 +15325,19 @@ var mountDAW = (target, options = {}) => {
14744
15325
  volume: masterVolume,
14745
15326
  drumVolume,
14746
15327
  reverb: reverbAmount,
15328
+ reverbDecay: Math.round(reverbDecay * 10),
15329
+ reverbPreDelay,
15330
+ delay: delayAmount,
15331
+ delayDivision,
15332
+ fadeIn: Math.round(fadeInSec * 10),
15333
+ fadeOut: Math.round(fadeOutSec * 10),
14747
15334
  mode,
14748
15335
  trackInstruments: trackInstMeta,
14749
15336
  trackCompression: trackCompMeta,
14750
- trackWidth: trackWidthMeta
15337
+ trackWidth: trackWidthMeta,
15338
+ trackEqLow: trackEqLowMeta,
15339
+ trackEqMid: trackEqMidMeta,
15340
+ trackEqHigh: trackEqHighMeta
14751
15341
  },
14752
15342
  ""
14753
15343
  );
@@ -14797,8 +15387,10 @@ var mountDAW = (target, options = {}) => {
14797
15387
  oct: t.vocalOctave,
14798
15388
  vib: t.vocalVibrato,
14799
15389
  rev: t.vocalReverb,
15390
+ del: t.vocalDelay,
14800
15391
  gen: t.vocalGender,
14801
- bre: t.vocalBreathiness
15392
+ bre: t.vocalBreathiness,
15393
+ dbl: t.vocalOctaveDouble
14802
15394
  })).filter(
14803
15395
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
14804
15396
  ).map((x2) => {
@@ -14809,8 +15401,10 @@ var mountDAW = (target, options = {}) => {
14809
15401
  x2.oct === 0 ? "" : `o${x2.oct}`,
14810
15402
  x2.vib ? "b1" : "",
14811
15403
  x2.rev === 0 ? "" : `r${x2.rev}`,
15404
+ x2.del === 0 ? "" : `e${x2.del}`,
14812
15405
  x2.gen === 50 ? "" : `g${x2.gen}`,
14813
- x2.bre === 50 ? "" : `h${x2.bre}`
15406
+ x2.bre === 50 ? "" : `h${x2.bre}`,
15407
+ x2.dbl ? "w1" : ""
14814
15408
  ].filter((s) => s.length > 0).join(" ");
14815
15409
  const head = params ? `${x2.model} ${params}` : x2.model;
14816
15410
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -14879,7 +15473,7 @@ var mountDAW = (target, options = {}) => {
14879
15473
  const canvas = getGridCanvas();
14880
15474
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
14881
15475
  const logicalY = yIndex * renderConfig.keyHeight;
14882
- currentOffsetY = clamp3(
15476
+ currentOffsetY = clamp4(
14883
15477
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
14884
15478
  0,
14885
15479
  getMaxOffsetY()
@@ -14956,6 +15550,39 @@ var mountDAW = (target, options = {}) => {
14956
15550
  refs.reverbAmountLabel.textContent = `${meta.reverb}%`;
14957
15551
  options.onReverbChange?.(meta.reverb);
14958
15552
  }
15553
+ if (meta.reverbDecay !== void 0) {
15554
+ reverbDecay = meta.reverbDecay / 10;
15555
+ refs.reverbDecay.value = String(meta.reverbDecay);
15556
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
15557
+ options.onReverbDecayChange?.(reverbDecay);
15558
+ }
15559
+ if (meta.reverbPreDelay !== void 0) {
15560
+ reverbPreDelay = meta.reverbPreDelay;
15561
+ refs.reverbPreDelay.value = String(meta.reverbPreDelay);
15562
+ refs.reverbPreDelayLabel.textContent = `${meta.reverbPreDelay}ms`;
15563
+ options.onReverbPreDelayChange?.(reverbPreDelay);
15564
+ }
15565
+ if (meta.delay !== void 0) {
15566
+ delayAmount = meta.delay;
15567
+ refs.delayAmount.value = String(meta.delay);
15568
+ refs.delayAmountLabel.textContent = `${meta.delay}%`;
15569
+ options.onDelayChange?.(meta.delay);
15570
+ }
15571
+ if (meta.delayDivision && ["4", "8", "8d", "16"].includes(meta.delayDivision)) {
15572
+ delayDivision = meta.delayDivision;
15573
+ refs.delayDivision.value = delayDivision;
15574
+ options.onDelayDivisionChange?.(delayDivision);
15575
+ }
15576
+ if (meta.fadeIn !== void 0) {
15577
+ fadeInSec = meta.fadeIn / 10;
15578
+ refs.fadeIn.value = String(fadeInSec);
15579
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
15580
+ }
15581
+ if (meta.fadeOut !== void 0) {
15582
+ fadeOutSec = meta.fadeOut / 10;
15583
+ refs.fadeOut.value = String(fadeOutSec);
15584
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
15585
+ }
14959
15586
  }
14960
15587
  trackStates.forEach((t, i2) => {
14961
15588
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -14977,6 +15604,21 @@ var mountDAW = (target, options = {}) => {
14977
15604
  t.trackWidth = width;
14978
15605
  options.onTrackWidthChange?.(t.config.id, width);
14979
15606
  }
15607
+ const eqLow = meta.trackEqLow?.[i2] ?? 0;
15608
+ if (t.trackEqLow !== eqLow) {
15609
+ t.trackEqLow = eqLow;
15610
+ options.onTrackEqLowChange?.(t.config.id, eqLow);
15611
+ }
15612
+ const eqMid = meta.trackEqMid?.[i2] ?? 0;
15613
+ if (t.trackEqMid !== eqMid) {
15614
+ t.trackEqMid = eqMid;
15615
+ options.onTrackEqMidChange?.(t.config.id, eqMid);
15616
+ }
15617
+ const eqHigh = meta.trackEqHigh?.[i2] ?? 0;
15618
+ if (t.trackEqHigh !== eqHigh) {
15619
+ t.trackEqHigh = eqHigh;
15620
+ options.onTrackEqHighChange?.(t.config.id, eqHigh);
15621
+ }
14980
15622
  });
14981
15623
  trackStates.forEach((t, i2) => {
14982
15624
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -14996,8 +15638,10 @@ var mountDAW = (target, options = {}) => {
14996
15638
  active.vocalOctave = 0;
14997
15639
  active.vocalVibrato = false;
14998
15640
  active.vocalReverb = 0;
15641
+ active.vocalDelay = 0;
14999
15642
  active.vocalGender = 50;
15000
15643
  active.vocalBreathiness = 50;
15644
+ active.vocalOctaveDouble = false;
15001
15645
  } else {
15002
15646
  for (const t of trackStates) {
15003
15647
  t.lyrics = "";
@@ -15008,8 +15652,10 @@ var mountDAW = (target, options = {}) => {
15008
15652
  t.vocalOctave = 0;
15009
15653
  t.vocalVibrato = false;
15010
15654
  t.vocalReverb = 0;
15655
+ t.vocalDelay = 0;
15011
15656
  t.vocalGender = 50;
15012
15657
  t.vocalBreathiness = 50;
15658
+ t.vocalOctaveDouble = false;
15013
15659
  }
15014
15660
  }
15015
15661
  lyrics?.forEach((lt) => {
@@ -15024,8 +15670,10 @@ var mountDAW = (target, options = {}) => {
15024
15670
  t.vocalOctave = lt.octave ?? 0;
15025
15671
  t.vocalVibrato = lt.vibrato ?? false;
15026
15672
  t.vocalReverb = lt.reverb ?? 0;
15673
+ t.vocalDelay = lt.delay ?? 0;
15027
15674
  t.vocalGender = lt.gender ?? 50;
15028
15675
  t.vocalBreathiness = lt.breathiness ?? 50;
15676
+ t.vocalOctaveDouble = lt.octaveDouble ?? false;
15029
15677
  });
15030
15678
  for (const p of placements) {
15031
15679
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -15106,8 +15754,10 @@ var mountDAW = (target, options = {}) => {
15106
15754
  active.vocalOctave = 0;
15107
15755
  active.vocalVibrato = false;
15108
15756
  active.vocalReverb = 0;
15757
+ active.vocalDelay = 0;
15109
15758
  active.vocalGender = 50;
15110
15759
  active.vocalBreathiness = 50;
15760
+ active.vocalOctaveDouble = false;
15111
15761
  } else {
15112
15762
  clearAll();
15113
15763
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -15120,8 +15770,10 @@ var mountDAW = (target, options = {}) => {
15120
15770
  t.vocalOctave = 0;
15121
15771
  t.vocalVibrato = false;
15122
15772
  t.vocalReverb = 0;
15773
+ t.vocalDelay = 0;
15123
15774
  t.vocalGender = 50;
15124
15775
  t.vocalBreathiness = 50;
15776
+ t.vocalOctaveDouble = false;
15125
15777
  }
15126
15778
  }
15127
15779
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -15170,6 +15822,7 @@ var mountDAW = (target, options = {}) => {
15170
15822
  bpm = value;
15171
15823
  refs.bpmInput.value = String(value);
15172
15824
  for (const t of trackStates) t.core.setTempo(value);
15825
+ options.onBpmChange?.(value);
15173
15826
  };
15174
15827
  let lastUndoTime = 0;
15175
15828
  const undo = () => {
@@ -15345,6 +15998,43 @@ var mountDAW = (target, options = {}) => {
15345
15998
  refs.reverbAmountInfoBtn.addEventListener("click", () => {
15346
15999
  showModal("\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC", MASTER_REVERB_INFO_HTML);
15347
16000
  });
16001
+ refs.reverbDecay.addEventListener("input", () => {
16002
+ const raw = Number.parseInt(refs.reverbDecay.value, 10) || 22;
16003
+ reverbDecay = Math.max(
16004
+ MIN_REVERB_DECAY_SEC,
16005
+ Math.min(MAX_REVERB_DECAY_SEC, raw / 10)
16006
+ );
16007
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
16008
+ options.onReverbDecayChange?.(reverbDecay);
16009
+ });
16010
+ refs.reverbPreDelay.addEventListener("input", () => {
16011
+ reverbPreDelay = Number.parseInt(refs.reverbPreDelay.value, 10) || 0;
16012
+ refs.reverbPreDelayLabel.textContent = `${reverbPreDelay}ms`;
16013
+ options.onReverbPreDelayChange?.(reverbPreDelay);
16014
+ });
16015
+ refs.delayAmount.addEventListener("input", () => {
16016
+ delayAmount = Number.parseInt(refs.delayAmount.value, 10) || 0;
16017
+ refs.delayAmountLabel.textContent = `${delayAmount}%`;
16018
+ options.onDelayChange?.(delayAmount);
16019
+ });
16020
+ refs.delayDivision.addEventListener("change", () => {
16021
+ delayDivision = refs.delayDivision.value;
16022
+ options.onDelayDivisionChange?.(delayDivision);
16023
+ });
16024
+ refs.delayAmountInfoBtn.addEventListener("click", () => {
16025
+ showModal("\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC", MASTER_DELAY_INFO_HTML);
16026
+ });
16027
+ refs.fadeIn.addEventListener("input", () => {
16028
+ fadeInSec = Number.parseFloat(refs.fadeIn.value) || 0;
16029
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
16030
+ });
16031
+ refs.fadeOut.addEventListener("input", () => {
16032
+ fadeOutSec = Number.parseFloat(refs.fadeOut.value) || 0;
16033
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
16034
+ });
16035
+ refs.fadeInfoBtn.addEventListener("click", () => {
16036
+ showModal("\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC", FADE_INFO_HTML);
16037
+ });
15348
16038
  refs.autoMasterInfoBtn.addEventListener("click", () => {
15349
16039
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15350
16040
  });
@@ -15353,14 +16043,39 @@ var mountDAW = (target, options = {}) => {
15353
16043
  refs.reverbAmount.value = "20";
15354
16044
  refs.reverbAmountLabel.textContent = "20%";
15355
16045
  options.onReverbChange?.(20);
16046
+ const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
16047
+ reverbDecay = clamp4(
16048
+ 3 - bpmT * (3 - 0.9),
16049
+ MIN_REVERB_DECAY_SEC,
16050
+ MAX_REVERB_DECAY_SEC
16051
+ );
16052
+ refs.reverbDecay.value = String(Math.round(reverbDecay * 10));
16053
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
16054
+ options.onReverbDecayChange?.(reverbDecay);
16055
+ reverbPreDelay = 20;
16056
+ refs.reverbPreDelay.value = "20";
16057
+ refs.reverbPreDelayLabel.textContent = "20ms";
16058
+ options.onReverbPreDelayChange?.(20);
16059
+ let mainVocalId = null;
16060
+ let mainVocalScore = -1;
16061
+ for (const t of trackStates) {
16062
+ if (!t.lyricModel) continue;
16063
+ const score = t.core.getNotes().reduce((sum, n) => sum + n.durationSteps, 0);
16064
+ if (score > mainVocalScore) {
16065
+ mainVocalScore = score;
16066
+ mainVocalId = t.config.id;
16067
+ }
16068
+ }
15356
16069
  for (const t of trackStates) {
15357
- t.trackCompression = 35;
16070
+ const isMainVocal = !!t.lyricModel && t.config.id === mainVocalId;
16071
+ const comp = !t.lyricModel ? 35 : isMainVocal ? 30 : 25;
16072
+ t.trackCompression = comp;
15358
16073
  t.trackWidth = 115;
15359
- options.onTrackCompressionChange?.(t.config.id, 35);
16074
+ options.onTrackCompressionChange?.(t.config.id, comp);
15360
16075
  options.onTrackWidthChange?.(t.config.id, 115);
15361
16076
  if (t.lyricModel) {
15362
16077
  t.vocalVibrato = true;
15363
- t.vocalReverb = 25;
16078
+ t.vocalReverb = isMainVocal ? 15 : 30;
15364
16079
  fireLyricsChange(t);
15365
16080
  }
15366
16081
  }
@@ -15597,6 +16312,20 @@ var mountDAW = (target, options = {}) => {
15597
16312
  redrawAll();
15598
16313
  })
15599
16314
  );
16315
+ refs.transposeApplyBtn.addEventListener(
16316
+ "click",
16317
+ () => overlayDuring(() => {
16318
+ transposeNotes(
16319
+ trackStates.map((t) => t.core),
16320
+ Number.parseInt(refs.transposeSelect.value, 10) || 0
16321
+ );
16322
+ redrawAll();
16323
+ updateUndoRedo();
16324
+ })
16325
+ );
16326
+ refs.transposeInfoBtn.addEventListener("click", () => {
16327
+ showModal("\u79FB\u8ABF\u306E\u89E3\u8AAC", TRANSPOSE_INFO_HTML);
16328
+ });
15600
16329
  if (showMidi) wireMidi();
15601
16330
  if (showMidiSearch) wireMidiSearch();
15602
16331
  document.addEventListener("keydown", onKeyDown);
@@ -16242,7 +16971,7 @@ var mountDAW = (target, options = {}) => {
16242
16971
  currentPlayStep = step;
16243
16972
  playbackState = "paused";
16244
16973
  const canvas = getGridCanvas();
16245
- currentOffsetX = clamp3(
16974
+ currentOffsetX = clamp4(
16246
16975
  step * renderConfig.stepWidth - canvas.width * 0.5,
16247
16976
  0,
16248
16977
  getMaxOffsetX()
@@ -16288,11 +17017,11 @@ var mountDAW = (target, options = {}) => {
16288
17017
  getViewState,
16289
17018
  setViewState: (state) => {
16290
17019
  if (typeof state.zoomX === "number") {
16291
- zoomX = clamp3(state.zoomX, 25, 200);
17020
+ zoomX = clamp4(state.zoomX, 25, 200);
16292
17021
  applyZoomX();
16293
17022
  }
16294
17023
  if (typeof state.zoomY === "number") {
16295
- zoomY = clamp3(state.zoomY, 50, 200);
17024
+ zoomY = clamp4(state.zoomY, 50, 200);
16296
17025
  applyZoomY();
16297
17026
  }
16298
17027
  if (typeof state.decomposeChord === "boolean") {
@@ -16314,28 +17043,52 @@ var mountDAW = (target, options = {}) => {
16314
17043
  forcePauseAt,
16315
17044
  setLoading,
16316
17045
  setMasterVolume: (volume) => {
16317
- masterVolume = clamp3(volume, 0, 100);
17046
+ masterVolume = clamp4(volume, 0, 100);
16318
17047
  refs.masterVolume.value = String(masterVolume);
16319
17048
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
16320
17049
  options.singingVoices?.setVolume(masterVolume / 100);
16321
17050
  },
16322
17051
  setVolume: (volume) => {
16323
- masterVolume = clamp3(volume, 0, 100);
17052
+ masterVolume = clamp4(volume, 0, 100);
16324
17053
  refs.masterVolume.value = String(masterVolume);
16325
17054
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
16326
17055
  options.singingVoices?.setVolume(masterVolume / 100);
16327
17056
  },
16328
17057
  setDrumVolume: (volume) => {
16329
- drumVolume = clamp3(volume, 0, 100);
17058
+ drumVolume = clamp4(volume, 0, 100);
16330
17059
  refs.drumVolume.value = String(drumVolume);
16331
17060
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
16332
17061
  },
16333
17062
  setReverbAmount: (amount) => {
16334
- reverbAmount = clamp3(amount, 0, 100);
17063
+ reverbAmount = clamp4(amount, 0, 100);
16335
17064
  refs.reverbAmount.value = String(reverbAmount);
16336
17065
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
16337
17066
  options.onReverbChange?.(reverbAmount);
16338
17067
  },
17068
+ setReverbDecay: (seconds) => {
17069
+ reverbDecay = Math.max(
17070
+ MIN_REVERB_DECAY_SEC,
17071
+ Math.min(MAX_REVERB_DECAY_SEC, seconds)
17072
+ );
17073
+ refs.reverbDecay.value = String(Math.round(reverbDecay * 10));
17074
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
17075
+ options.onReverbDecayChange?.(reverbDecay);
17076
+ },
17077
+ setReverbPreDelay: (ms) => {
17078
+ reverbPreDelay = Math.max(
17079
+ MIN_REVERB_PREDELAY_MS,
17080
+ Math.min(MAX_REVERB_PREDELAY_MS, ms)
17081
+ );
17082
+ refs.reverbPreDelay.value = String(reverbPreDelay);
17083
+ refs.reverbPreDelayLabel.textContent = `${reverbPreDelay}ms`;
17084
+ options.onReverbPreDelayChange?.(reverbPreDelay);
17085
+ },
17086
+ setDelayAmount: (amount) => {
17087
+ delayAmount = clamp4(amount, 0, 100);
17088
+ refs.delayAmount.value = String(delayAmount);
17089
+ refs.delayAmountLabel.textContent = `${delayAmount}%`;
17090
+ options.onDelayChange?.(delayAmount);
17091
+ },
16339
17092
  applyPatch: (trackId, added, removed) => {
16340
17093
  const track = trackStates.find((t) => t.config.id === trackId);
16341
17094
  if (!track) return;
@@ -16377,8 +17130,10 @@ var mountDAW = (target, options = {}) => {
16377
17130
  t.vocalOctave = data.vocalOctave;
16378
17131
  t.vocalVibrato = data.vocalVibrato ?? false;
16379
17132
  t.vocalReverb = data.vocalReverb ?? 0;
17133
+ t.vocalDelay = data.vocalDelay ?? 0;
16380
17134
  t.vocalGender = data.vocalGender ?? 50;
16381
17135
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17136
+ t.vocalOctaveDouble = data.vocalOctaveDouble ?? false;
16382
17137
  },
16383
17138
  applyTrackInstrument: (trackIndex, instrumentName) => {
16384
17139
  const t = trackStates[trackIndex];
@@ -16497,8 +17252,10 @@ var playSingingMML = async (mml, options = {}) => {
16497
17252
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16498
17253
  vibrato: lt.vibrato,
16499
17254
  reverbSend: (lt.reverb ?? 0) / 100,
17255
+ delaySend: (lt.delay ?? 0) / 100,
16500
17256
  gender: (lt.gender ?? 50) / 100,
16501
17257
  breathiness: (lt.breathiness ?? 50) / 100,
17258
+ octaveDouble: lt.octaveDouble,
16502
17259
  notes
16503
17260
  };
16504
17261
  });
@@ -17107,9 +17864,9 @@ var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
17107
17864
  URL.revokeObjectURL(url);
17108
17865
 
17109
17866
  // src/channel-strip.ts
17110
- var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17867
+ var clamp5 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17111
17868
  var compressionParams = (amount) => {
17112
- const t = clamp4(amount, 0, 100) / 100;
17869
+ const t = clamp5(amount, 0, 100) / 100;
17113
17870
  return {
17114
17871
  threshold: 0 + (-24 - 0) * t,
17115
17872
  ratio: 1 + (12 - 1) * t,
@@ -17118,8 +17875,29 @@ var compressionParams = (amount) => {
17118
17875
  release: 0.25 + (0.15 - 0.25) * t
17119
17876
  };
17120
17877
  };
17878
+ var EQ_LOW_FREQ = 200;
17879
+ var EQ_MID_FREQ = 1e3;
17880
+ var EQ_HIGH_FREQ = 5e3;
17881
+ var EQ_MID_Q = 1;
17882
+ var EQ_MAX_DB = 12;
17121
17883
  var createChannelStrip = (ctx, destination, options = {}) => {
17122
17884
  const input = ctx.createGain();
17885
+ const eqLow = ctx.createBiquadFilter();
17886
+ eqLow.type = "lowshelf";
17887
+ eqLow.frequency.value = EQ_LOW_FREQ;
17888
+ eqLow.gain.value = clamp5(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17889
+ const eqMid = ctx.createBiquadFilter();
17890
+ eqMid.type = "peaking";
17891
+ eqMid.frequency.value = EQ_MID_FREQ;
17892
+ eqMid.Q.value = EQ_MID_Q;
17893
+ eqMid.gain.value = clamp5(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17894
+ const eqHigh = ctx.createBiquadFilter();
17895
+ eqHigh.type = "highshelf";
17896
+ eqHigh.frequency.value = EQ_HIGH_FREQ;
17897
+ eqHigh.gain.value = clamp5(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
17898
+ input.connect(eqLow);
17899
+ eqLow.connect(eqMid);
17900
+ eqMid.connect(eqHigh);
17123
17901
  const compressor = ctx.createDynamicsCompressor();
17124
17902
  const applyCompression = (amount) => {
17125
17903
  const p = compressionParams(amount);
@@ -17162,21 +17940,45 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17162
17940
  outR.connect(merger, 0, 1);
17163
17941
  const setWidth = (width) => {
17164
17942
  widthGain.gain.setTargetAtTime(
17165
- clamp4(width, 0, 200) / 100,
17943
+ clamp5(width, 0, 200) / 100,
17166
17944
  ctx.currentTime,
17167
17945
  0.02
17168
17946
  );
17169
17947
  };
17170
17948
  setWidth(options.width ?? 100);
17171
- input.connect(compressor);
17949
+ eqHigh.connect(compressor);
17172
17950
  compressor.connect(splitter);
17173
17951
  merger.connect(destination);
17174
17952
  return {
17175
17953
  input,
17954
+ setEqLow: (db) => {
17955
+ eqLow.gain.setTargetAtTime(
17956
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17957
+ ctx.currentTime,
17958
+ 0.02
17959
+ );
17960
+ },
17961
+ setEqMid: (db) => {
17962
+ eqMid.gain.setTargetAtTime(
17963
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17964
+ ctx.currentTime,
17965
+ 0.02
17966
+ );
17967
+ },
17968
+ setEqHigh: (db) => {
17969
+ eqHigh.gain.setTargetAtTime(
17970
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
17971
+ ctx.currentTime,
17972
+ 0.02
17973
+ );
17974
+ },
17176
17975
  setCompression: applyCompression,
17177
17976
  setWidth,
17178
17977
  dispose: () => {
17179
17978
  input.disconnect();
17979
+ eqLow.disconnect();
17980
+ eqMid.disconnect();
17981
+ eqHigh.disconnect();
17180
17982
  compressor.disconnect();
17181
17983
  splitter.disconnect();
17182
17984
  mid.disconnect();
@@ -17247,25 +18049,6 @@ var createClipMeter = (ctx, source, options = {}) => {
17247
18049
  };
17248
18050
  };
17249
18051
 
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
18052
  // src/sf/SoundFont_drum.ts
17270
18053
  var touch = (map, key, ctor) => {
17271
18054
  if (!map.has(key)) map.set(key, new ctor());
@@ -17420,12 +18203,16 @@ var createDtmStudio = async (options = {}) => {
17420
18203
  const drumGain = audioCtx.createGain();
17421
18204
  drumGain.gain.value = options.drumVolume ?? 1;
17422
18205
  drumGain.connect(masterGain);
18206
+ let reverbDecaySec = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
18207
+ const reverbPreDelay = audioCtx.createDelay(MAX_REVERB_PREDELAY_MS / 1e3);
18208
+ reverbPreDelay.delayTime.value = (options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS) / 1e3;
17423
18209
  const reverbConvolver = audioCtx.createConvolver();
17424
- reverbConvolver.buffer = createReverbImpulse(audioCtx);
18210
+ reverbConvolver.buffer = createReverbImpulse(audioCtx, reverbDecaySec);
17425
18211
  reverbConvolver.normalize = true;
17426
18212
  const reverbWetGain = audioCtx.createGain();
17427
18213
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
17428
- masterGain.connect(reverbConvolver);
18214
+ masterGain.connect(reverbPreDelay);
18215
+ reverbPreDelay.connect(reverbConvolver);
17429
18216
  reverbConvolver.connect(reverbWetGain);
17430
18217
  const setReverbAmount = (amount) => {
17431
18218
  reverbWetGain.gain.setTargetAtTime(
@@ -17434,9 +18221,28 @@ var createDtmStudio = async (options = {}) => {
17434
18221
  0.02
17435
18222
  );
17436
18223
  };
18224
+ const setReverbDecay = (seconds) => {
18225
+ reverbDecaySec = Math.max(
18226
+ MIN_REVERB_DECAY_SEC,
18227
+ Math.min(MAX_REVERB_DECAY_SEC, seconds)
18228
+ );
18229
+ reverbConvolver.buffer = createReverbImpulse(audioCtx, reverbDecaySec);
18230
+ };
18231
+ const setReverbPreDelay = (ms) => {
18232
+ reverbPreDelay.delayTime.setTargetAtTime(
18233
+ Math.max(MIN_REVERB_PREDELAY_MS, Math.min(MAX_REVERB_PREDELAY_MS, ms)) / 1e3,
18234
+ audioCtx.currentTime,
18235
+ 0.02
18236
+ );
18237
+ };
17437
18238
  const finalMix = audioCtx.createGain();
17438
18239
  masterGain.connect(finalMix);
17439
18240
  reverbWetGain.connect(finalMix);
18241
+ const delayBus = createDelayBus(audioCtx, finalMix, {
18242
+ amount: options.delayAmount ?? 0,
18243
+ division: options.delayDivision ?? "8"
18244
+ });
18245
+ const setDelayAmount = (amount) => delayBus.setAmount(amount);
17440
18246
  const safetyLimiter = audioCtx.createDynamicsCompressor();
17441
18247
  safetyLimiter.threshold.value = -1;
17442
18248
  safetyLimiter.knee.value = 0;
@@ -17444,7 +18250,28 @@ var createDtmStudio = async (options = {}) => {
17444
18250
  safetyLimiter.attack.value = 1e-3;
17445
18251
  safetyLimiter.release.value = 0.1;
17446
18252
  finalMix.connect(safetyLimiter);
17447
- safetyLimiter.connect(options.destination ?? audioCtx.destination);
18253
+ const fadeGain = audioCtx.createGain();
18254
+ fadeGain.gain.value = 1;
18255
+ safetyLimiter.connect(fadeGain);
18256
+ fadeGain.connect(options.destination ?? audioCtx.destination);
18257
+ const scheduleFade = (params) => {
18258
+ fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
18259
+ if (!params) {
18260
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18261
+ return;
18262
+ }
18263
+ const { fadeInStartAt, fadeInEndAt, fadeOutStartAt, fadeOutEndAt } = params;
18264
+ if (fadeInStartAt !== void 0 && fadeInEndAt !== void 0) {
18265
+ fadeGain.gain.setValueAtTime(0, fadeInStartAt);
18266
+ fadeGain.gain.linearRampToValueAtTime(1, fadeInEndAt);
18267
+ } else {
18268
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18269
+ }
18270
+ if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
18271
+ fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
18272
+ fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
18273
+ }
18274
+ };
17448
18275
  const clipMeter = createClipMeter(audioCtx, finalMix);
17449
18276
  const channelStrips = /* @__PURE__ */ new Map();
17450
18277
  const getChannelStrip = (trackId) => {
@@ -17489,6 +18316,8 @@ var createDtmStudio = async (options = {}) => {
17489
18316
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
17490
18317
  // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
17491
18318
  reverbBus: reverbConvolver,
18319
+ // ボーカルトラック個別のディレイセンド(`e`トークン)先。
18320
+ delayBus: delayBus.input,
17492
18321
  // トラック単位チャンネルストリップ(コンプレッサー/ステレオワイド)の入口。
17493
18322
  // 楽器と同じ getChannelStrip キャッシュを共有するので、演奏トラックと歌詞トラックが
17494
18323
  // 同じ trackId を指していれば同じ処理が掛かる。
@@ -17769,8 +18598,23 @@ var createDtmStudio = async (options = {}) => {
17769
18598
  },
17770
18599
  reverbAmount: options.reverbAmount,
17771
18600
  onReverbChange: setReverbAmount,
18601
+ reverbDecay: options.reverbDecay,
18602
+ onReverbDecayChange: setReverbDecay,
18603
+ reverbPreDelay: options.reverbPreDelay,
18604
+ onReverbPreDelayChange: setReverbPreDelay,
18605
+ delayAmount: options.delayAmount,
18606
+ onDelayChange: setDelayAmount,
18607
+ delayDivision: options.delayDivision,
18608
+ onDelayDivisionChange: (division) => delayBus.setDivision(division),
18609
+ onBpmChange: (bpm) => delayBus.setBpm(bpm),
18610
+ fadeInSec: options.fadeInSec,
18611
+ fadeOutSec: options.fadeOutSec,
18612
+ onScheduleFade: scheduleFade,
17772
18613
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
17773
18614
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18615
+ onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
18616
+ onTrackEqMidChange: (trackId, db) => getChannelStrip(trackId).setEqMid(db),
18617
+ onTrackEqHighChange: (trackId, db) => getChannelStrip(trackId).setEqHigh(db),
17774
18618
  clipMeter,
17775
18619
  ...dawOverrides,
17776
18620
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
@@ -18307,6 +19151,7 @@ var createDtmStudio = async (options = {}) => {
18307
19151
  mountedChordPlayers.length = 0;
18308
19152
  mountedEditors.length = 0;
18309
19153
  clipMeter.dispose();
19154
+ delayBus.dispose();
18310
19155
  for (const strip of channelStrips.values()) strip.dispose();
18311
19156
  channelStrips.clear();
18312
19157
  void audioCtx.close();
@@ -18348,6 +19193,9 @@ var createDtmStudio = async (options = {}) => {
18348
19193
  setMasterVolume,
18349
19194
  setVolume: setMasterVolume,
18350
19195
  setReverbAmount,
19196
+ setReverbDecay,
19197
+ setReverbPreDelay,
19198
+ setDelayAmount,
18351
19199
  dispose
18352
19200
  };
18353
19201
  };
@@ -18456,5 +19304,6 @@ export {
18456
19304
  stripCustomVocals,
18457
19305
  stripLyrics,
18458
19306
  stripMmlMeta,
19307
+ transposeNotes,
18459
19308
  vocalVolumeToGain
18460
19309
  };