@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.js CHANGED
@@ -124,6 +124,7 @@ __export(index_exports, {
124
124
  stripCustomVocals: () => stripCustomVocals,
125
125
  stripLyrics: () => stripLyrics,
126
126
  stripMmlMeta: () => stripMmlMeta,
127
+ transposeNotes: () => transposeNotes,
127
128
  vocalVolumeToGain: () => vocalVolumeToGain
128
129
  });
129
130
  module.exports = __toCommonJS(index_exports);
@@ -2316,10 +2317,12 @@ var parseLyrics = (mml) => {
2316
2317
  let octave = 0;
2317
2318
  let vibrato = false;
2318
2319
  let reverb = 0;
2320
+ let delay = 0;
2319
2321
  let gender = 50;
2320
2322
  let breathiness = 50;
2323
+ let octaveDouble = false;
2321
2324
  const modelMatch = rest.match(
2322
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrgh]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2325
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2323
2326
  );
2324
2327
  let model = "";
2325
2328
  const metaTokens = [];
@@ -2388,6 +2391,20 @@ var parseLyrics = (mml) => {
2388
2391
  rest = rest.substring(hMatch[0].length).trim();
2389
2392
  continue;
2390
2393
  }
2394
+ const eMatch = rest.match(/^e(\d+)/i);
2395
+ if (eMatch) {
2396
+ delay = clamp(Number.parseInt(eMatch[1], 10), 0, 100);
2397
+ metaTokens.push(eMatch[0]);
2398
+ rest = rest.substring(eMatch[0].length).trim();
2399
+ continue;
2400
+ }
2401
+ const wMatch = rest.match(/^w([01])/i);
2402
+ if (wMatch) {
2403
+ octaveDouble = wMatch[1] === "1";
2404
+ metaTokens.push(wMatch[0]);
2405
+ rest = rest.substring(wMatch[0].length).trim();
2406
+ continue;
2407
+ }
2391
2408
  break;
2392
2409
  }
2393
2410
  const lyricLines = [rest];
@@ -2404,8 +2421,10 @@ var parseLyrics = (mml) => {
2404
2421
  octave,
2405
2422
  vibrato,
2406
2423
  reverb,
2424
+ delay,
2407
2425
  gender,
2408
2426
  breathiness,
2427
+ octaveDouble,
2409
2428
  syllables,
2410
2429
  metaText: metaTokens.join(" "),
2411
2430
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2491,7 +2510,7 @@ var FORMANTS = {
2491
2510
  N: [250, 1e3]
2492
2511
  };
2493
2512
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
2494
- var createKlattVoice = (ctx, destination, reverbBus) => {
2513
+ var createKlattVoice = (ctx, destination, reverbBus, delayBus) => {
2495
2514
  const active = /* @__PURE__ */ new Set();
2496
2515
  const voice = (syllable, e) => {
2497
2516
  const t0 = ctx.currentTime + e.when;
@@ -2510,11 +2529,17 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2510
2529
  panner.connect(dest);
2511
2530
  out = panner;
2512
2531
  }
2513
- let sendGain = null;
2532
+ let reverbSendGain = null;
2514
2533
  if (reverbBus && e.reverbSend && e.reverbSend > 0 && panner) {
2515
- sendGain = ctx.createGain();
2516
- sendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2517
- panner.connect(sendGain).connect(reverbBus);
2534
+ reverbSendGain = ctx.createGain();
2535
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, e.reverbSend));
2536
+ panner.connect(reverbSendGain).connect(reverbBus);
2537
+ }
2538
+ let delaySendGain = null;
2539
+ if (delayBus && e.delaySend && e.delaySend > 0 && panner) {
2540
+ delaySendGain = ctx.createGain();
2541
+ delaySendGain.gain.value = Math.max(0, Math.min(1, e.delaySend));
2542
+ panner.connect(delaySendGain).connect(delayBus);
2518
2543
  }
2519
2544
  const osc = ctx.createOscillator();
2520
2545
  osc.type = "sawtooth";
@@ -2571,7 +2596,8 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2571
2596
  active.delete(osc);
2572
2597
  osc.disconnect();
2573
2598
  panner?.disconnect();
2574
- sendGain?.disconnect();
2599
+ reverbSendGain?.disconnect();
2600
+ delaySendGain?.disconnect();
2575
2601
  };
2576
2602
  };
2577
2603
  voice.stopAll = () => {
@@ -2887,7 +2913,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2887
2913
  return p;
2888
2914
  };
2889
2915
  const LEADCAP_S = 0.09;
2890
- const schedule = (r, t0, peak, pan, reverbSend = 0, destOverride) => {
2916
+ const schedule = (r, t0, peak, pan, reverbSend = 0, delaySend = 0, destOverride) => {
2891
2917
  const dest = destOverride ?? destination;
2892
2918
  let out = dest;
2893
2919
  let panner = null;
@@ -2897,11 +2923,17 @@ var createKoeVoice = async (ctx, destination, options) => {
2897
2923
  panner.connect(dest);
2898
2924
  out = panner;
2899
2925
  }
2900
- let sendGain = null;
2926
+ let reverbSendGain = null;
2901
2927
  if (options.reverbBus && reverbSend > 0 && panner) {
2902
- sendGain = ctx.createGain();
2903
- sendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2904
- panner.connect(sendGain).connect(options.reverbBus);
2928
+ reverbSendGain = ctx.createGain();
2929
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
2930
+ panner.connect(reverbSendGain).connect(options.reverbBus);
2931
+ }
2932
+ let delaySendGain = null;
2933
+ if (options.delayBus && delaySend > 0 && panner) {
2934
+ delaySendGain = ctx.createGain();
2935
+ delaySendGain.gain.value = Math.max(0, Math.min(1, delaySend));
2936
+ panner.connect(delaySendGain).connect(options.delayBus);
2905
2937
  }
2906
2938
  const src = ctx.createBufferSource();
2907
2939
  src.buffer = r.audio;
@@ -2928,7 +2960,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2928
2960
  src.disconnect();
2929
2961
  env.disconnect();
2930
2962
  panner?.disconnect();
2931
- sendGain?.disconnect();
2963
+ reverbSendGain?.disconnect();
2964
+ delaySendGain?.disconnect();
2932
2965
  };
2933
2966
  };
2934
2967
  const model = (syllable, e) => {
@@ -2947,7 +2980,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2947
2980
  const pan = e.pan ?? 0;
2948
2981
  const durationMs = Math.max(60, e.duration * 1e3);
2949
2982
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2950
- if (r) schedule(r, t0, peak, pan, e.reverbSend, e.destination);
2983
+ if (r)
2984
+ schedule(r, t0, peak, pan, e.reverbSend, e.delaySend, e.destination);
2951
2985
  });
2952
2986
  };
2953
2987
  model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato, expr) => {
@@ -2965,9 +2999,9 @@ var createKoeVoice = async (ctx, destination, options) => {
2965
2999
  const r = await renderInto(alias, pitch, dMs, vib, expr);
2966
3000
  return r ? keyOf(alias, pitch, dMs, vib, expr) : null;
2967
3001
  };
2968
- model.scheduleCached = (key, t0, peak, pan, reverbSend, dest) => {
3002
+ model.scheduleCached = (key, t0, peak, pan, reverbSend, delaySend, dest) => {
2969
3003
  const r = renderCache.get(key);
2970
- if (r) schedule(r, t0, peak, pan, reverbSend, dest);
3004
+ if (r) schedule(r, t0, peak, pan, reverbSend, delaySend, dest);
2971
3005
  };
2972
3006
  model.stopAll = () => {
2973
3007
  for (const src of active) {
@@ -2987,6 +3021,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2987
3021
  var PREWARM_NOTES = 3;
2988
3022
  var STREAM_LOOKAHEAD_SEC = 1.5;
2989
3023
  var STREAM_POLL_MS = 100;
3024
+ var OCTAVE_DOUBLE_SEMITONES = -12;
3025
+ var OCTAVE_DOUBLE_PEAK_SCALE = 0.6;
2990
3026
  var FALLBACK_MODEL = "klatt";
2991
3027
  var createSingingVoices = (ctx, destination, options = {}) => {
2992
3028
  const catalog = {};
@@ -2997,8 +3033,12 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2997
3033
  let streamSession = 0;
2998
3034
  const masterGain = ctx.createGain();
2999
3035
  masterGain.connect(destination);
3036
+ let masterVolumeScalar = 1;
3000
3037
  const loaded = /* @__PURE__ */ new Map([
3001
- [FALLBACK_MODEL, createKlattVoice(ctx, masterGain, options.reverbBus)]
3038
+ [
3039
+ FALLBACK_MODEL,
3040
+ createKlattVoice(ctx, masterGain, options.reverbBus, options.delayBus)
3041
+ ]
3002
3042
  ]);
3003
3043
  const loading = /* @__PURE__ */ new Map();
3004
3044
  const load2 = (model) => {
@@ -3019,7 +3059,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3019
3059
  worldlineScriptUrl: options.worldlineScriptUrl,
3020
3060
  lightweight: options.lightweight,
3021
3061
  voiceWorkerUrl: options.voiceWorkerUrl,
3022
- reverbBus: options.reverbBus
3062
+ reverbBus: options.reverbBus,
3063
+ delayBus: options.delayBus
3023
3064
  })
3024
3065
  ))().then((v) => {
3025
3066
  loaded.set(m, v);
@@ -3068,7 +3109,24 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3068
3109
  forEachSungNote(track, (note, prevVowel) => {
3069
3110
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
3070
3111
  n++;
3071
- tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
3112
+ tasks.push({
3113
+ model: m,
3114
+ note,
3115
+ prevVowel,
3116
+ pitch: note.pitch,
3117
+ vibrato: track.vibrato,
3118
+ expr
3119
+ });
3120
+ if (track.octaveDouble) {
3121
+ tasks.push({
3122
+ model: m,
3123
+ note,
3124
+ prevVowel,
3125
+ pitch: note.pitch + OCTAVE_DOUBLE_SEMITONES,
3126
+ vibrato: track.vibrato,
3127
+ expr
3128
+ });
3129
+ }
3072
3130
  });
3073
3131
  }
3074
3132
  const total = tasks.length;
@@ -3082,7 +3140,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3082
3140
  await (task.model.renderToCache?.(
3083
3141
  task.note.syllable,
3084
3142
  task.prevVowel,
3085
- task.note.pitch,
3143
+ task.pitch,
3086
3144
  task.note.durationSec * 1e3,
3087
3145
  task.vibrato,
3088
3146
  task.expr
@@ -3122,53 +3180,70 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3122
3180
  }
3123
3181
  if (opts?.isAudible && !opts.isAudible(track)) continue;
3124
3182
  const t0 = anchorTime + startSec;
3125
- if (model.renderToCache && model.scheduleCached) {
3126
- const renderToCache = model.renderToCache;
3127
- const scheduleCached = model.scheduleCached;
3128
- void (async () => {
3129
- const key = await renderToCache(
3130
- note.syllable,
3131
- prevVowel,
3132
- note.pitch,
3133
- note.durationSec * 1e3,
3134
- track.vibrato,
3135
- { gender: track.gender, breathiness: track.breathiness }
3136
- );
3137
- if (session !== streamSession) return;
3138
- if (key) {
3139
- const delay = ctx.currentTime - t0;
3140
- if (delay < 0.05) {
3141
- scheduleCached(
3142
- key,
3143
- t0,
3144
- peak,
3145
- track.pan,
3146
- track.reverbSend,
3147
- options.getTrackDestination?.(track.id ?? "")
3148
- );
3149
- opts?.onScheduled?.(track, note, t0);
3150
- } else {
3151
- console.warn(
3152
- `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3153
- );
3154
- opts?.onLateSkip?.(note, delay);
3183
+ const dispatchNote = (pitch, peakScale) => {
3184
+ if (model.renderToCache && model.scheduleCached) {
3185
+ const renderToCache = model.renderToCache;
3186
+ const scheduleCached = model.scheduleCached;
3187
+ void (async () => {
3188
+ const key = await renderToCache(
3189
+ note.syllable,
3190
+ prevVowel,
3191
+ pitch,
3192
+ note.durationSec * 1e3,
3193
+ track.vibrato,
3194
+ { gender: track.gender, breathiness: track.breathiness }
3195
+ );
3196
+ if (session !== streamSession) return;
3197
+ if (key) {
3198
+ const delay = ctx.currentTime - t0;
3199
+ if (delay < 0.05) {
3200
+ const dest = options.getTrackDestination?.(track.id ?? "");
3201
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3202
+ scheduleCached(
3203
+ key,
3204
+ t0,
3205
+ effPeak,
3206
+ track.pan,
3207
+ track.reverbSend,
3208
+ track.delaySend,
3209
+ dest
3210
+ );
3211
+ opts?.onScheduled?.(track, note, t0);
3212
+ } else {
3213
+ console.warn(
3214
+ `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3215
+ );
3216
+ opts?.onLateSkip?.(note, delay);
3217
+ }
3155
3218
  }
3156
- }
3157
- })();
3158
- } else {
3159
- const when = t0 - ctx.currentTime;
3160
- model(note.syllable, {
3161
- trackId: track.id ?? "",
3162
- pitch: note.pitch,
3163
- velocity: 100,
3164
- volume: peak,
3165
- when,
3166
- duration: note.durationSec,
3167
- pan: track.pan,
3168
- reverbSend: track.reverbSend,
3169
- destination: options.getTrackDestination?.(track.id ?? "")
3170
- });
3171
- opts?.onScheduled?.(track, note, t0);
3219
+ })();
3220
+ } else {
3221
+ const when = t0 - ctx.currentTime;
3222
+ const dest = options.getTrackDestination?.(track.id ?? "");
3223
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3224
+ model(note.syllable, {
3225
+ trackId: track.id ?? "",
3226
+ pitch,
3227
+ velocity: 100,
3228
+ volume: effPeak,
3229
+ when,
3230
+ duration: note.durationSec,
3231
+ pan: track.pan,
3232
+ reverbSend: track.reverbSend,
3233
+ delaySend: track.delaySend,
3234
+ destination: dest
3235
+ });
3236
+ opts?.onScheduled?.(track, note, t0);
3237
+ }
3238
+ };
3239
+ dispatchNote(note.pitch, 1);
3240
+ if (track.octaveDouble) {
3241
+ dispatchNote(
3242
+ note.pitch + OCTAVE_DOUBLE_SEMITONES,
3243
+ OCTAVE_DOUBLE_PEAK_SCALE
3244
+ );
3245
+ }
3246
+ if (!(model.renderToCache && model.scheduleCached)) {
3172
3247
  await new Promise((resolve) => setTimeout(resolve, 0));
3173
3248
  }
3174
3249
  }
@@ -3191,7 +3266,9 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3191
3266
  for (const v of loaded.values()) v.reset?.();
3192
3267
  };
3193
3268
  const setVolume = (gain) => {
3194
- masterGain.gain.value = Math.max(0, gain);
3269
+ const g = Math.max(0, gain);
3270
+ masterGain.gain.value = g;
3271
+ masterVolumeScalar = g;
3195
3272
  };
3196
3273
  return {
3197
3274
  loadModels,
@@ -3225,10 +3302,13 @@ var PITCH_MAP = {
3225
3302
  b: 11
3226
3303
  };
3227
3304
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3228
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|mode)=([\w-]+)/gi;
3305
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3229
3306
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3230
3307
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3231
3308
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3309
+ var TRACK_EQLOW_DIRECTIVE = /#t(\d+)eqlo=(-?\d+)/gi;
3310
+ var TRACK_EQMID_DIRECTIVE = /#t(\d+)eqmid=(-?\d+)/gi;
3311
+ var TRACK_EQHIGH_DIRECTIVE = /#t(\d+)eqhi=(-?\d+)/gi;
3232
3312
  var parseMmlMeta = (mml) => {
3233
3313
  const meta = {};
3234
3314
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -3245,6 +3325,23 @@ var parseMmlMeta = (mml) => {
3245
3325
  } else if (key === "reverb") {
3246
3326
  const rv = Number.parseInt(m[2], 10);
3247
3327
  if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3328
+ } else if (key === "reverbdecay") {
3329
+ const rd = Number.parseInt(m[2], 10);
3330
+ if (!Number.isNaN(rd)) meta.reverbDecay = clamp2(rd, 3, 40);
3331
+ } else if (key === "reverbpredelay") {
3332
+ const rp = Number.parseInt(m[2], 10);
3333
+ if (!Number.isNaN(rp)) meta.reverbPreDelay = clamp2(rp, 0, 150);
3334
+ } else if (key === "delay") {
3335
+ const dv = Number.parseInt(m[2], 10);
3336
+ if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3337
+ } else if (key === "delaydiv") {
3338
+ if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3339
+ } else if (key === "fadein") {
3340
+ const fi = Number.parseInt(m[2], 10);
3341
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3342
+ } else if (key === "fadeout") {
3343
+ const fo = Number.parseInt(m[2], 10);
3344
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3248
3345
  } else if (key === "mode") {
3249
3346
  if (m[2] === "simple" || m[2] === "advanced") {
3250
3347
  meta.mode = m[2];
@@ -3275,9 +3372,33 @@ var parseMmlMeta = (mml) => {
3275
3372
  meta.trackWidth[idx] = val;
3276
3373
  }
3277
3374
  }
3375
+ for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3376
+ const idx = Number.parseInt(m[1], 10);
3377
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3378
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3379
+ meta.trackEqLow ??= {};
3380
+ meta.trackEqLow[idx] = val;
3381
+ }
3382
+ }
3383
+ for (const m of mml.matchAll(TRACK_EQMID_DIRECTIVE)) {
3384
+ const idx = Number.parseInt(m[1], 10);
3385
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3386
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3387
+ meta.trackEqMid ??= {};
3388
+ meta.trackEqMid[idx] = val;
3389
+ }
3390
+ }
3391
+ for (const m of mml.matchAll(TRACK_EQHIGH_DIRECTIVE)) {
3392
+ const idx = Number.parseInt(m[1], 10);
3393
+ const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
3394
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3395
+ meta.trackEqHigh ??= {};
3396
+ meta.trackEqHigh[idx] = val;
3397
+ }
3398
+ }
3278
3399
  return meta;
3279
3400
  };
3280
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "");
3401
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "").replace(TRACK_EQLOW_DIRECTIVE, "").replace(TRACK_EQMID_DIRECTIVE, "").replace(TRACK_EQHIGH_DIRECTIVE, "");
3281
3402
  var formatMmlMeta = (meta, space = "") => {
3282
3403
  const parts = [];
3283
3404
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3288,6 +3409,18 @@ var formatMmlMeta = (meta, space = "") => {
3288
3409
  parts.push(`#drumvolume=${meta.drumVolume}`);
3289
3410
  if (meta.reverb !== void 0 && meta.reverb !== 0)
3290
3411
  parts.push(`#reverb=${meta.reverb}`);
3412
+ if (meta.reverbDecay !== void 0 && meta.reverbDecay !== 22)
3413
+ parts.push(`#reverbdecay=${meta.reverbDecay}`);
3414
+ if (meta.reverbPreDelay !== void 0 && meta.reverbPreDelay !== 0)
3415
+ parts.push(`#reverbpredelay=${meta.reverbPreDelay}`);
3416
+ if (meta.delay !== void 0 && meta.delay !== 0)
3417
+ parts.push(`#delay=${meta.delay}`);
3418
+ if (meta.delayDivision && meta.delayDivision !== "8")
3419
+ parts.push(`#delaydiv=${meta.delayDivision}`);
3420
+ if (meta.fadeIn !== void 0 && meta.fadeIn !== 0)
3421
+ parts.push(`#fadein=${meta.fadeIn}`);
3422
+ if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
3423
+ parts.push(`#fadeout=${meta.fadeOut}`);
3291
3424
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3292
3425
  if (meta.trackInstruments) {
3293
3426
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
@@ -3304,6 +3437,21 @@ var formatMmlMeta = (meta, space = "") => {
3304
3437
  if (val !== 100) parts.push(`#t${idx}width=${val}`);
3305
3438
  }
3306
3439
  }
3440
+ if (meta.trackEqLow) {
3441
+ for (const [idx, val] of Object.entries(meta.trackEqLow)) {
3442
+ if (val !== 0) parts.push(`#t${idx}eqlo=${val}`);
3443
+ }
3444
+ }
3445
+ if (meta.trackEqMid) {
3446
+ for (const [idx, val] of Object.entries(meta.trackEqMid)) {
3447
+ if (val !== 0) parts.push(`#t${idx}eqmid=${val}`);
3448
+ }
3449
+ }
3450
+ if (meta.trackEqHigh) {
3451
+ for (const [idx, val] of Object.entries(meta.trackEqHigh)) {
3452
+ if (val !== 0) parts.push(`#t${idx}eqhi=${val}`);
3453
+ }
3454
+ }
3307
3455
  return parts.join(space);
3308
3456
  };
3309
3457
  var parseMML = (mml, options = {}) => {
@@ -9139,8 +9287,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9139
9287
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
9140
9288
  vibrato: lt.vibrato,
9141
9289
  reverbSend: (lt.reverb ?? 0) / 100,
9290
+ delaySend: (lt.delay ?? 0) / 100,
9142
9291
  gender: (lt.gender ?? 50) / 100,
9143
9292
  breathiness: (lt.breathiness ?? 50) / 100,
9293
+ octaveDouble: lt.octaveDouble,
9144
9294
  notes
9145
9295
  };
9146
9296
  });
@@ -10818,6 +10968,71 @@ var mountChordPlayer = (target, chords, options = {}) => {
10818
10968
  };
10819
10969
  };
10820
10970
 
10971
+ // src/delay.ts
10972
+ var DELAY_DIVISIONS = [
10973
+ { value: "4", label: "4\u5206", beats: 1 },
10974
+ { value: "8", label: "8\u5206", beats: 0.5 },
10975
+ { value: "8d", label: "\u4ED8\u70B98\u5206", beats: 0.75 },
10976
+ { value: "16", label: "16\u5206", beats: 0.25 }
10977
+ ];
10978
+ var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
10979
+ var divisionToSeconds = (division, bpm) => {
10980
+ const beats = DELAY_DIVISIONS.find((d) => d.value === division)?.beats ?? 0.5;
10981
+ const safeBpm = bpm > 0 ? bpm : 120;
10982
+ return 60 / safeBpm * beats;
10983
+ };
10984
+ var DELAY_MAX_WET = 0.45;
10985
+ var delayAmountToGain = (amount) => clamp3(amount, 0, 100) / 100 * DELAY_MAX_WET;
10986
+ var FEEDBACK_GAIN = 0.3;
10987
+ var MAX_DELAY_SEC = 2;
10988
+ var createDelayBus = (ctx, destination, options = {}) => {
10989
+ const input = ctx.createGain();
10990
+ const delayNode = ctx.createDelay(MAX_DELAY_SEC);
10991
+ const feedback = ctx.createGain();
10992
+ feedback.gain.value = FEEDBACK_GAIN;
10993
+ const wetGain = ctx.createGain();
10994
+ wetGain.gain.value = delayAmountToGain(options.amount ?? 0);
10995
+ input.connect(delayNode);
10996
+ delayNode.connect(feedback);
10997
+ feedback.connect(delayNode);
10998
+ delayNode.connect(wetGain);
10999
+ wetGain.connect(destination);
11000
+ let bpm = options.bpm ?? 120;
11001
+ let division = options.division ?? "8";
11002
+ const applyTime = () => {
11003
+ delayNode.delayTime.setTargetAtTime(
11004
+ divisionToSeconds(division, bpm),
11005
+ ctx.currentTime,
11006
+ 0.05
11007
+ );
11008
+ };
11009
+ applyTime();
11010
+ return {
11011
+ input,
11012
+ setAmount: (amount) => {
11013
+ wetGain.gain.setTargetAtTime(
11014
+ delayAmountToGain(amount),
11015
+ ctx.currentTime,
11016
+ 0.02
11017
+ );
11018
+ },
11019
+ setDivision: (d) => {
11020
+ division = d;
11021
+ applyTime();
11022
+ },
11023
+ setBpm: (b) => {
11024
+ bpm = b;
11025
+ applyTime();
11026
+ },
11027
+ dispose: () => {
11028
+ input.disconnect();
11029
+ delayNode.disconnect();
11030
+ feedback.disconnect();
11031
+ wetGain.disconnect();
11032
+ }
11033
+ };
11034
+ };
11035
+
10821
11036
  // src/daw-ui.ts
10822
11037
  var q = (root, sel) => root.querySelector(sel);
10823
11038
  var buildUI = (target, options) => {
@@ -10934,6 +11149,36 @@ var buildUI = (target, options) => {
10934
11149
  <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10935
11150
  <button class="dtm-infobtn" data-dtm="reverb-amount-info" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10936
11151
  </div>
11152
+ <div class="dtm-row">
11153
+ <span class="dtm-label">Decay</span>
11154
+ <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">
11155
+ <span class="dtm-label" data-dtm="reverb-decay-label">2.2s</span>
11156
+ </div>
11157
+ <div class="dtm-row">
11158
+ <span class="dtm-label">Pre Delay</span>
11159
+ <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">
11160
+ <span class="dtm-label" data-dtm="reverb-predelay-label">0ms</span>
11161
+ </div>
11162
+ <div class="dtm-row">
11163
+ <span class="dtm-label">\u30C7\u30A3\u30EC\u30A4</span>
11164
+ <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">
11165
+ <span class="dtm-label" data-dtm="delay-amount-label">0%</span>
11166
+ <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">
11167
+ ${DELAY_DIVISIONS.map((d) => `<option value="${d.value}">${d.label}</option>`).join("")}
11168
+ </select>
11169
+ <button class="dtm-infobtn" data-dtm="delay-amount-info" title="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11170
+ </div>
11171
+ <div class="dtm-row">
11172
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3</span>
11173
+ <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">
11174
+ <span class="dtm-label" data-dtm="fade-in-label">0.0s</span>
11175
+ <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>
11176
+ </div>
11177
+ <div class="dtm-row">
11178
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8</span>
11179
+ <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">
11180
+ <span class="dtm-label" data-dtm="fade-out-label">0.0s</span>
11181
+ </div>
10937
11182
  </div>
10938
11183
  </details>
10939
11184
 
@@ -11018,6 +11263,25 @@ var buildUI = (target, options) => {
11018
11263
  </select>
11019
11264
  <button class="dtm-btn dtm-btn--primary" data-dtm="shift-apply">\u9069\u7528</button>
11020
11265
  </div>
11266
+ <div class="dtm-row">
11267
+ <span class="dtm-label">\u79FB\u8ABF</span>
11268
+ <select class="dtm-select" data-dtm="transpose-select" aria-label="\u79FB\u8ABF\u3059\u308B\u534A\u97F3\u6570">
11269
+ <option value="-12">-1oct</option>
11270
+ <option value="-7">-5th</option>
11271
+ <option value="-5">-4th</option>
11272
+ <option value="-3">-3\u534A\u97F3</option>
11273
+ <option value="-2">-2\u534A\u97F3</option>
11274
+ <option value="-1">-1\u534A\u97F3</option>
11275
+ <option value="1">+1\u534A\u97F3</option>
11276
+ <option value="2">+2\u534A\u97F3</option>
11277
+ <option value="3">+3\u534A\u97F3</option>
11278
+ <option value="5">+4th</option>
11279
+ <option value="7">+5th</option>
11280
+ <option value="12">+1oct</option>
11281
+ </select>
11282
+ <button class="dtm-btn dtm-btn--primary" data-dtm="transpose-apply">\u9069\u7528</button>
11283
+ <button class="dtm-infobtn" data-dtm="transpose-info" title="\u79FB\u8ABF\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11284
+ </div>
11021
11285
  <div class="dtm-row">
11022
11286
  <button class="dtm-btn dtm-btn--danger" data-dtm="macro-clear">\u5168\u6D88\u53BB</button>
11023
11287
  <button class="dtm-btn dtm-btn--accent" data-dtm="macro-random">\u30E9\u30F3\u30C0\u30E0\u914D\u7F6E</button>
@@ -11132,6 +11396,19 @@ var buildUI = (target, options) => {
11132
11396
  reverbAmount: sel("reverb-amount"),
11133
11397
  reverbAmountLabel: sel("reverb-amount-label"),
11134
11398
  reverbAmountInfoBtn: sel("reverb-amount-info"),
11399
+ reverbDecay: sel("reverb-decay"),
11400
+ reverbDecayLabel: sel("reverb-decay-label"),
11401
+ reverbPreDelay: sel("reverb-predelay"),
11402
+ reverbPreDelayLabel: sel("reverb-predelay-label"),
11403
+ delayAmount: sel("delay-amount"),
11404
+ delayAmountLabel: sel("delay-amount-label"),
11405
+ delayAmountInfoBtn: sel("delay-amount-info"),
11406
+ delayDivision: sel("delay-division"),
11407
+ fadeIn: sel("fade-in"),
11408
+ fadeInLabel: sel("fade-in-label"),
11409
+ fadeOut: sel("fade-out"),
11410
+ fadeOutLabel: sel("fade-out-label"),
11411
+ fadeInfoBtn: sel("fade-info"),
11135
11412
  autoMasterBtn: sel("auto-master"),
11136
11413
  autoMasterInfoBtn: sel("auto-master-info"),
11137
11414
  trackTabs: sel("track-tabs"),
@@ -11152,6 +11429,9 @@ var buildUI = (target, options) => {
11152
11429
  applyActiveOnly: sel("apply-active-only"),
11153
11430
  shiftSelect: sel("shift-select"),
11154
11431
  shiftApplyBtn: sel("shift-apply"),
11432
+ transposeSelect: sel("transpose-select"),
11433
+ transposeApplyBtn: sel("transpose-apply"),
11434
+ transposeInfoBtn: sel("transpose-info"),
11155
11435
  macroClear: sel("macro-clear"),
11156
11436
  macroRandom: sel("macro-random"),
11157
11437
  macroHarmonic: sel("macro-harmonic"),
@@ -11406,6 +11686,18 @@ var shiftNotes = (cores, shiftSteps) => {
11406
11686
  }
11407
11687
  }
11408
11688
  };
11689
+ var transposeNotes = (cores, semitones) => {
11690
+ if (semitones === 0) return;
11691
+ for (const core of cores) {
11692
+ const notes = [...core.getNotes()];
11693
+ for (const note of notes) {
11694
+ const newPitch = Math.max(0, Math.min(127, note.pitch + semitones));
11695
+ if (newPitch !== note.pitch) {
11696
+ core.moveNote(note.id, note.startStep, newPitch);
11697
+ }
11698
+ }
11699
+ }
11700
+ };
11409
11701
 
11410
11702
  // src/midi-io.ts
11411
11703
  var STEPS_PER_BEAT3 = 48;
@@ -12765,6 +13057,38 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
12765
13057
  return chordNotes / notes.length >= threshold;
12766
13058
  };
12767
13059
 
13060
+ // src/reverb.ts
13061
+ var DEFAULT_REVERB_DECAY_SEC = 2.2;
13062
+ var MIN_REVERB_DECAY_SEC = 0.3;
13063
+ var MAX_REVERB_DECAY_SEC = 4;
13064
+ var IMPULSE_DECAY_CURVE = 2.5;
13065
+ var DEFAULT_REVERB_PREDELAY_MS = 0;
13066
+ var MIN_REVERB_PREDELAY_MS = 0;
13067
+ var MAX_REVERB_PREDELAY_MS = 150;
13068
+ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
13069
+ const rate = ctx.sampleRate;
13070
+ const length = Math.max(
13071
+ 1,
13072
+ Math.floor(
13073
+ rate * Math.max(
13074
+ MIN_REVERB_DECAY_SEC,
13075
+ Math.min(MAX_REVERB_DECAY_SEC, decaySec)
13076
+ )
13077
+ )
13078
+ );
13079
+ const impulse = ctx.createBuffer(2, length, rate);
13080
+ for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
13081
+ const data = impulse.getChannelData(ch);
13082
+ for (let i2 = 0; i2 < length; i2++) {
13083
+ const envelope = (1 - i2 / length) ** IMPULSE_DECAY_CURVE;
13084
+ data[i2] = (Math.random() * 2 - 1) * envelope;
13085
+ }
13086
+ }
13087
+ return impulse;
13088
+ };
13089
+ var REVERB_MAX_WET = 0.6;
13090
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
13091
+
12768
13092
  // src/daw.ts
12769
13093
  var CHORD_INFO_HTML2 = `
12770
13094
  <div class="dtm-modal-body-content">
@@ -12806,6 +13130,16 @@ var VIBRATO_INFO_HTML = `
12806
13130
  <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>
12807
13131
  </div>
12808
13132
  `;
13133
+ var OCTAVE_DOUBLE_INFO_HTML = `
13134
+ <div class="dtm-modal-body-content">
13135
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13136
+ <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>
13137
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13138
+ <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>
13139
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13140
+ <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>
13141
+ </div>
13142
+ `;
12809
13143
  var GENDER_INFO_HTML = `
12810
13144
  <div class="dtm-modal-body-content">
12811
13145
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -12836,14 +13170,66 @@ var LYRIC_REVERB_INFO_HTML = `
12836
13170
  <p>\u6B8B\u97FF\u3067\u97F3\u304C\u6EF2\u307F\u3001\u6B4C\u8A5E\u304C\u805E\u304D\u53D6\u308A\u306B\u304F\u304F\u306A\u308A\u307E\u3059\u3002\u30C9\u30E9\u30A4\u306A\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u8FD1\u3044\u300D\u300C\u524D\u306B\u51FA\u308B\u300D\u5370\u8C61\u3001\u30EA\u30D0\u30FC\u30D6\u305F\u3063\u3077\u308A\u306E\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u5965\u884C\u304D\u304C\u3042\u308B\u300D\u300C\u5E7B\u60F3\u7684\u300D\u306A\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u3002\u8868\u73FE\u3057\u305F\u3044\u8DDD\u96E2\u611F\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
12837
13171
  </div>
12838
13172
  `;
12839
- var MASTER_REVERB_INFO_HTML = `
13173
+ var LYRIC_DELAY_INFO_HTML = `
12840
13174
  <div class="dtm-modal-body-content">
12841
13175
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12842
- <p>\u66F2\u5168\u4F53\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308B\u6B8B\u97FF\uFF08\u90E8\u5C4B\u9CF4\u308A\u30FB\u7A7A\u9593\u306E\u97FF\u304D\uFF09\u3067\u3059\u30020%\u3067\u5B8C\u5168\u306B\u30C9\u30E9\u30A4\uFF08\u97FF\u304D\u306A\u3057\uFF09\u3001\u4E0A\u3052\u308B\u307B\u3069\u5E83\u3044\u7A7A\u9593\u3067\u9CF4\u3063\u3066\u3044\u308B\u3088\u3046\u306A\u5965\u884C\u304D\u304C\u51FA\u307E\u3059\u3002</p>
13176
+ <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>
13177
+ <h4>\u30DE\u30B9\u30BF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u300D\u3064\u307E\u307F\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
13178
+ <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>
13179
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13180
+ <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>
13181
+ </div>
13182
+ `;
13183
+ var MASTER_REVERB_INFO_HTML = `
13184
+ <div class="dtm-modal-body-content">
13185
+ <h4>\u30EA\u30D0\u30FC\u30D6\uFF08Mix\uFF09</h4>
13186
+ <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>
13187
+ <h4>Decay\uFF08\u6B8B\u97FF\u306E\u9577\u3055\uFF09</h4>
13188
+ <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>
13189
+ <h4>Pre Delay\uFF08\u539F\u97F3\u3068\u30EA\u30D0\u30FC\u30D6\u306E\u9593\u9694\uFF09</h4>
13190
+ <p>\u539F\u97F3\u304C\u9CF4\u3063\u3066\u304B\u3089\u6B8B\u97FF\u304C\u7ACB\u3061\u4E0A\u304C\u308B\u307E\u3067\u306E\u9045\u5EF6\u3067\u3059\u30020ms\u3060\u3068\u539F\u97F3\u3068\u6B8B\u97FF\u304C\u540C\u6642\u306B\u59CB\u307E\u308A\u97F3\u50CF\u304C\u307C\u3084\u3051\u304C\u3061\u3067\u3059\u304C\u3001\u5C11\u3057\uFF08\u6570\u5341ms\u7A0B\u5EA6\uFF09\u5165\u308C\u308B\u3068\u539F\u97F3\u306E\u8F2A\u90ED\u30FB\u30A2\u30BF\u30C3\u30AF\u611F\u3092\u4FDD\u3063\u305F\u307E\u307E\u5965\u884C\u304D\u3092\u8DB3\u305B\u307E\u3059\u3002\u7279\u306B\u30DC\u30FC\u30AB\u30EB\u3067\u52B9\u679C\u7684\u3067\u3059\u3002</p>
12843
13191
  <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12844
- <p>\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u305D\u308C\u305E\u308C\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u3044\u3046\u500B\u5225\u306E\u3064\u307E\u307F\u304C\u3042\u308A\u3001\u305D\u3061\u3089\u304C0%\u306E\u30C8\u30E9\u30C3\u30AF\u306F\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3001\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002\u300C\u30DE\u30B9\u30BF\uFF1D\u6B8B\u97FF\u306E\u8CEA\u3068\u91CF\u305D\u306E\u3082\u306E\u300D\u300C\u30C8\u30E9\u30C3\u30AF\u9001\u308A\uFF1D\u305D\u306E\u30C8\u30E9\u30C3\u30AF\u3092\u3069\u308C\u3060\u3051\u6DF7\u305C\u308B\u304B\u300D\u3068\u3044\u3046\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u697D\u5668\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u73FE\u72B6\u30BB\u30F3\u30C9\u6A5F\u80FD\u304C\u7121\u3044\u305F\u3081\u3001\u5E38\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308A\u307E\u3059\u3002</p>
12845
- <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12846
- <p>\u30DF\u30C3\u30AF\u30B9\u5168\u4F53\u306E\u8F2A\u90ED\u304C\u307C\u3084\u3051\u3001\u9060\u304F\u30FB\u3053\u3082\u3063\u305F\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u300210\u301C30%\u7A0B\u5EA6\u3092\u51FA\u767A\u70B9\u306B\u3001\u66F2\u306E\u30B8\u30E3\u30F3\u30EB\u3084\u7A7A\u9593\u306E\u5E83\u3055\u306E\u30A4\u30E1\u30FC\u30B8\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3059\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
13192
+ <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>
13193
+ </div>
13194
+ `;
13195
+ var MASTER_DELAY_INFO_HTML = `
13196
+ <div class="dtm-modal-body-content">
13197
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13198
+ <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>
13199
+ <h4>\u30EA\u30D0\u30FC\u30D6\u3068\u306E\u9055\u3044</h4>
13200
+ <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>
13201
+ <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2</h4>
13202
+ <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>
13203
+ </div>
13204
+ `;
13205
+ var FADE_INFO_HTML = `
13206
+ <div class="dtm-modal-body-content">
13207
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13208
+ <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>
13209
+ <h4>\u3044\u3064\u52B9\u304F\u304B</h4>
13210
+ <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>
13211
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13212
+ <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>
13213
+ </div>
13214
+ `;
13215
+ var TRANSPOSE_INFO_HTML = `
13216
+ <div class="dtm-modal-body-content">
13217
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13218
+ <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>
13219
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13220
+ <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>
13221
+ <h4>\u6CE8\u610F</h4>
13222
+ <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>
13223
+ </div>
13224
+ `;
13225
+ var TRACK_EQ_INFO_HTML = `
13226
+ <div class="dtm-modal-body-content">
13227
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13228
+ <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>
13229
+ <h4>\u4F7F\u3044\u3069\u3053\u308D\uFF08\u5E2F\u57DF\u306E\u68F2\u307F\u5206\u3051\uFF09</h4>
13230
+ <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>
13231
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044\uFF08\u9806\u5E8F\u304C\u91CD\u8981\uFF09</h4>
13232
+ <p>\u3053\u306E\u30C1\u30E3\u30F3\u30CD\u30EB\u30B9\u30C8\u30EA\u30C3\u30D7\u3067\u306F EQ \u2192 \u97F3\u5727\u5F37\u5316\uFF08\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\uFF09 \u2192 \u30B9\u30C6\u30EC\u30AA\u5E45 \u306E\u9806\u3067\u51E6\u7406\u3055\u308C\u307E\u3059\u3002\u3053\u308C\u306F\u4E00\u822C\u7684\u306A\u30DF\u30C3\u30AF\u30B9\u306E\u5B9A\u77F3\u3068\u540C\u3058\u3067\u3059\u3002\u4E0D\u8981\u306A\u5E2F\u57DF\uFF08\u4F4E\u57DF\u306E\u3053\u3082\u308A\u3001\u8033\u969C\u308A\u306A\u9AD8\u57DF\u7B49\uFF09\u3092\u5148\u306BEQ\u3067\u524A\u3063\u3066\u304B\u3089\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u3092\u639B\u3051\u308B\u3068\u3001\u30B3\u30F3\u30D7\u304C\u672C\u5F53\u306B\u5FC5\u8981\u306A\u97F3\u3060\u3051\u306B\u53CD\u5FDC\u3059\u308B\u3088\u3046\u306B\u306A\u308A\u3001\u97F3\u5727\u5F37\u5316\u306E\u52B9\u304D\u304C\u826F\u304F\u306A\u308A\u307E\u3059\u3002\u9806\u5E8F\u3092\u5909\u3048\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093\u3002</p>
12847
13233
  </div>
12848
13234
  `;
12849
13235
  var TRACK_COMPRESSION_INFO_HTML = `
@@ -12869,14 +13255,22 @@ var TRACK_WIDTH_INFO_HTML = `
12869
13255
  var AUTO_MASTER_INFO_HTML = `
12870
13256
  <div class="dtm-modal-body-content">
12871
13257
  <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
12872
- <p>\u5E02\u8CA9\u66F2\u3067\u3088\u304F\u4F7F\u308F\u308C\u308B\u5024\u3092\u76EE\u5B89\u306B\u3001\u4EE5\u4E0B\u3092\u307E\u3068\u3081\u3066\u8A2D\u5B9A\u3059\u308B\u30DC\u30BF\u30F3\u3067\u3059\u3002</p>
13258
+ <p>\u5E02\u8CA9\u66F2\u3067\u3088\u304F\u4F7F\u308F\u308C\u308B\u5024\u3092\u76EE\u5B89\u306B\u3001\u4EE5\u4E0B\u3092\u307E\u3068\u3081\u3066\u8A2D\u5B9A\u3059\u308B\u30DC\u30BF\u30F3\u3067\u3059\u3002\u66F2\u306EBPM\u3092\u898B\u3066\u4E00\u90E8\u306E\u5024\u3092\u81EA\u52D5\u8ABF\u6574\u3057\u307E\u3059\u3002</p>
12873
13259
  <ul>
12874
- <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6: 20%</li>
12875
- <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: 35%</li>
13260
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6 Mix: 20%</li>
13261
+ <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>
13262
+ <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>
13263
+ <li>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: \u697D\u566835%\u30FB\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF\u306F25%\uFF08\u5F37\u304F\u6F70\u3059\u3068\u8868\u60C5\u304C\u5931\u308F\u308C\u308B\u305F\u3081\u63A7\u3048\u3081\u306B\uFF09</li>
12876
13264
  <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45: 115%\uFF08\u308F\u305A\u304B\u306B\u5E83\u3052\u308B\uFF09</li>
12877
- <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON\u3001\u30EA\u30D0\u30FC\u30D6\u9001\u308A25%</li>
13265
+ <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON</li>
13266
+ </ul>
13267
+ <h4>\u30E1\u30A4\u30F3\u30DC\u30FC\u30AB\u30EB\u306E\u81EA\u52D5\u5224\u5B9A</h4>
13268
+ <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>
13269
+ <ul>
13270
+ <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>
13271
+ <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>
12878
13272
  </ul>
12879
- <p style="margin-top:4px;"><small>\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
13273
+ <p style="margin-top:4px;"><small>\u300C\u524D\u306B\u51FA\u3057\u305F\u3044\u97F3\u306F\u30EA\u30D0\u30FC\u30D6\u5C11\u306A\u3081\u3001\u5965\u306B\u7F6E\u304D\u305F\u3044\u97F3\u306F\u30EA\u30D0\u30FC\u30D6\u591A\u3081\u300D\u3068\u3044\u3046\u5B9A\u77F3\u306B\u57FA\u3065\u3044\u3066\u3044\u307E\u3059\u3002EQ\u306F\u66F2\u30FB\u30D1\u30FC\u30C8\u3054\u3068\u306B\u4F55\u304C\u6FC1\u3063\u3066\u3044\u308B\u304B\u6B21\u7B2C\u3067\u81EA\u52D5\u5316\u306B\u5411\u304B\u306A\u3044\u305F\u3081\u5BFE\u8C61\u5916\u3067\u3059\u3002\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
12880
13274
  </div>
12881
13275
  `;
12882
13276
  var MIDI_INFO_HTML = `
@@ -13112,7 +13506,7 @@ var deriveCustomVocalKeyFromUrl = (url2) => {
13112
13506
  if (name && /^[0-9]/.test(name)) name = `_${name}`;
13113
13507
  return CUSTOM_VOCAL_KEY_RE.test(name) ? name : "";
13114
13508
  };
13115
- var clamp3 = (v, min, max) => Math.min(Math.max(v, min), max);
13509
+ var clamp4 = (v, min, max) => Math.min(Math.max(v, min), max);
13116
13510
  var normalizeInstrumentName = (name) => {
13117
13511
  if (!name) return "";
13118
13512
  const stripped = name.replace(/\s+/g, "").toLowerCase();
@@ -13152,6 +13546,21 @@ var mountDAW = (target, options = {}) => {
13152
13546
  refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
13153
13547
  refs.reverbAmount.value = String(options.reverbAmount ?? 0);
13154
13548
  refs.reverbAmountLabel.textContent = `${options.reverbAmount ?? 0}%`;
13549
+ {
13550
+ const initDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
13551
+ const initPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13552
+ refs.reverbDecay.value = String(Math.round(initDecay * 10));
13553
+ refs.reverbDecayLabel.textContent = `${initDecay.toFixed(1)}s`;
13554
+ refs.reverbPreDelay.value = String(initPreDelay);
13555
+ refs.reverbPreDelayLabel.textContent = `${initPreDelay}ms`;
13556
+ }
13557
+ refs.delayAmount.value = String(options.delayAmount ?? 0);
13558
+ refs.delayAmountLabel.textContent = `${options.delayAmount ?? 0}%`;
13559
+ refs.delayDivision.value = options.delayDivision ?? "8";
13560
+ refs.fadeIn.value = String(options.fadeInSec ?? 0);
13561
+ refs.fadeInLabel.textContent = `${(options.fadeInSec ?? 0).toFixed(1)}s`;
13562
+ refs.fadeOut.value = String(options.fadeOutSec ?? 0);
13563
+ refs.fadeOutLabel.textContent = `${(options.fadeOutSec ?? 0).toFixed(1)}s`;
13155
13564
  refs.drumVolume.value = String(options.drumVolume ?? 80);
13156
13565
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
13157
13566
  const renderConfig = {
@@ -13168,8 +13577,18 @@ var mountDAW = (target, options = {}) => {
13168
13577
  let masterVolume = options.masterVolume ?? 50;
13169
13578
  options.singingVoices?.setVolume(masterVolume / 100);
13170
13579
  let reverbAmount = options.reverbAmount ?? 0;
13580
+ let reverbDecay = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
13581
+ let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13582
+ let delayAmount = options.delayAmount ?? 0;
13583
+ let delayDivision = options.delayDivision ?? "8";
13584
+ let fadeInSec = options.fadeInSec ?? 0;
13585
+ let fadeOutSec = options.fadeOutSec ?? 0;
13171
13586
  let unsubscribeClip;
13172
13587
  options.onReverbChange?.(reverbAmount);
13588
+ options.onReverbDecayChange?.(reverbDecay);
13589
+ options.onReverbPreDelayChange?.(reverbPreDelay);
13590
+ options.onDelayChange?.(delayAmount);
13591
+ options.onDelayDivisionChange?.(delayDivision);
13173
13592
  let drumVolume = options.drumVolume ?? 80;
13174
13593
  let currentDrumPattern = refs.drumSelect.value;
13175
13594
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
@@ -13250,8 +13669,10 @@ var mountDAW = (target, options = {}) => {
13250
13669
  vocalOctave: t.vocalOctave,
13251
13670
  vocalVibrato: t.vocalVibrato,
13252
13671
  vocalReverb: t.vocalReverb,
13672
+ vocalDelay: t.vocalDelay,
13253
13673
  vocalGender: t.vocalGender,
13254
- vocalBreathiness: t.vocalBreathiness
13674
+ vocalBreathiness: t.vocalBreathiness,
13675
+ vocalOctaveDouble: t.vocalOctaveDouble
13255
13676
  };
13256
13677
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13257
13678
  lyricsDebounceTimer = setTimeout(() => {
@@ -13313,11 +13734,16 @@ var mountDAW = (target, options = {}) => {
13313
13734
  vocalOctave: 0,
13314
13735
  vocalVibrato: false,
13315
13736
  vocalReverb: 0,
13737
+ vocalDelay: 0,
13316
13738
  vocalGender: 50,
13317
13739
  vocalBreathiness: 50,
13740
+ vocalOctaveDouble: false,
13318
13741
  trackInstrument: "",
13319
13742
  trackCompression: 0,
13320
- trackWidth: 100
13743
+ trackWidth: 100,
13744
+ trackEqLow: 0,
13745
+ trackEqMid: 0,
13746
+ trackEqHigh: 0
13321
13747
  };
13322
13748
  });
13323
13749
  };
@@ -13338,8 +13764,10 @@ var mountDAW = (target, options = {}) => {
13338
13764
  octave: t.vocalOctave,
13339
13765
  vibrato: t.vocalVibrato,
13340
13766
  reverb: t.vocalReverb,
13767
+ delay: t.vocalDelay,
13341
13768
  gender: t.vocalGender,
13342
13769
  breathiness: t.vocalBreathiness,
13770
+ octaveDouble: t.vocalOctaveDouble,
13343
13771
  syllables
13344
13772
  });
13345
13773
  });
@@ -13347,16 +13775,18 @@ var mountDAW = (target, options = {}) => {
13347
13775
  };
13348
13776
  const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
13349
13777
  let showModal;
13350
- const getMaxNoteStep = () => {
13778
+ const getSongEndStepExact = () => {
13351
13779
  let maxEndStep = 0;
13352
13780
  for (const t of trackStates) {
13353
13781
  for (const n of t.core.getNotes()) {
13354
13782
  const end = n.startStep + n.durationSteps;
13355
- if (end > maxEndStep) {
13356
- maxEndStep = end;
13357
- }
13783
+ if (end > maxEndStep) maxEndStep = end;
13358
13784
  }
13359
13785
  }
13786
+ return maxEndStep;
13787
+ };
13788
+ const getMaxNoteStep = () => {
13789
+ const maxEndStep = getSongEndStepExact();
13360
13790
  if (maxEndStep === 0) {
13361
13791
  return renderConfig.stepsPerBar * 4;
13362
13792
  }
@@ -13460,7 +13890,7 @@ var mountDAW = (target, options = {}) => {
13460
13890
  const thumbW = Math.max(40, canvas.width / totalContentWidth * sbW);
13461
13891
  const ratio = currentOffsetX / maxOffsetX;
13462
13892
  refs.hScrollThumb.style.width = `${thumbW}px`;
13463
- refs.hScrollThumb.style.left = `${clamp3(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13893
+ refs.hScrollThumb.style.left = `${clamp4(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
13464
13894
  }
13465
13895
  const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
13466
13896
  const sbH = refs.vScroll.clientHeight;
@@ -13530,9 +13960,9 @@ var mountDAW = (target, options = {}) => {
13530
13960
  if (maxOffsetX <= 0) return;
13531
13961
  const rect = refs.hScroll.getBoundingClientRect();
13532
13962
  const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
13533
- const x2 = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13963
+ const x2 = clamp4(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
13534
13964
  const ratio = x2 / (rect.width - thumbW);
13535
- currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
13965
+ currentOffsetX = clamp4(ratio * maxOffsetX, 0, maxOffsetX);
13536
13966
  setDrawOffset(currentOffsetX, currentOffsetY);
13537
13967
  redrawAll();
13538
13968
  };
@@ -13541,9 +13971,9 @@ var mountDAW = (target, options = {}) => {
13541
13971
  if (maxOffset <= 0) return;
13542
13972
  const rect = refs.vScroll.getBoundingClientRect();
13543
13973
  const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
13544
- const y = clamp3(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13974
+ const y = clamp4(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
13545
13975
  const ratio = y / (rect.height - thumbH);
13546
- currentOffsetY = clamp3(ratio * maxOffset, 0, maxOffset);
13976
+ currentOffsetY = clamp4(ratio * maxOffset, 0, maxOffset);
13547
13977
  setDrawOffset(currentOffsetX, currentOffsetY);
13548
13978
  redrawAll();
13549
13979
  };
@@ -13590,8 +14020,8 @@ var mountDAW = (target, options = {}) => {
13590
14020
  if (dx !== 0 || dy !== 0) {
13591
14021
  const maxOffsetX = getMaxOffsetX();
13592
14022
  const maxOffsetY = getMaxOffsetY();
13593
- currentOffsetX = clamp3(currentOffsetX + dx, 0, maxOffsetX);
13594
- currentOffsetY = clamp3(currentOffsetY + dy, 0, maxOffsetY);
14023
+ currentOffsetX = clamp4(currentOffsetX + dx, 0, maxOffsetX);
14024
+ currentOffsetY = clamp4(currentOffsetY + dy, 0, maxOffsetY);
13595
14025
  setDrawOffset(currentOffsetX, currentOffsetY);
13596
14026
  onPointerMove(lastMoveEvent);
13597
14027
  }
@@ -13981,12 +14411,12 @@ var mountDAW = (target, options = {}) => {
13981
14411
  "wheel",
13982
14412
  (event) => {
13983
14413
  event.preventDefault();
13984
- currentOffsetY = clamp3(
14414
+ currentOffsetY = clamp4(
13985
14415
  currentOffsetY + event.deltaY,
13986
14416
  0,
13987
14417
  getMaxOffsetY()
13988
14418
  );
13989
- currentOffsetX = clamp3(
14419
+ currentOffsetX = clamp4(
13990
14420
  currentOffsetX + event.deltaX,
13991
14421
  0,
13992
14422
  getMaxOffsetX()
@@ -14025,7 +14455,7 @@ var mountDAW = (target, options = {}) => {
14025
14455
  const centerStep = (currentOffsetX + canvas.width / 2) / renderConfig.stepWidth;
14026
14456
  renderConfig.stepWidth = BASE_STEP_WIDTH * (zoomX * 2) / 100;
14027
14457
  refs.zoomXLabel.textContent = `${zoomX}%`;
14028
- currentOffsetX = clamp3(
14458
+ currentOffsetX = clamp4(
14029
14459
  centerStep * renderConfig.stepWidth - canvas.width / 2,
14030
14460
  0,
14031
14461
  getMaxOffsetX()
@@ -14038,7 +14468,7 @@ var mountDAW = (target, options = {}) => {
14038
14468
  const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
14039
14469
  renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
14040
14470
  refs.zoomYLabel.textContent = `${zoomY}%`;
14041
- currentOffsetY = clamp3(
14471
+ currentOffsetY = clamp4(
14042
14472
  centerKey * renderConfig.keyHeight - canvas.height / 2,
14043
14473
  0,
14044
14474
  getMaxOffsetY()
@@ -14088,7 +14518,7 @@ var mountDAW = (target, options = {}) => {
14088
14518
  const threshold = currentOffsetX / renderConfig.stepWidth + visibleSteps - 4;
14089
14519
  if (currentPlayStep > threshold) {
14090
14520
  const visibleBars = Math.round(visibleSteps / renderConfig.stepsPerBar);
14091
- currentOffsetX = clamp3(
14521
+ currentOffsetX = clamp4(
14092
14522
  currentOffsetX + visibleBars * renderConfig.stepsPerBar * renderConfig.stepWidth,
14093
14523
  0,
14094
14524
  getMaxOffsetX()
@@ -14144,8 +14574,10 @@ var mountDAW = (target, options = {}) => {
14144
14574
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
14145
14575
  vibrato: lt.vibrato,
14146
14576
  reverbSend: (lt.reverb ?? 0) / 100,
14577
+ delaySend: (lt.delay ?? 0) / 100,
14147
14578
  gender: (lt.gender ?? 50) / 100,
14148
14579
  breathiness: (lt.breathiness ?? 50) / 100,
14580
+ octaveDouble: lt.octaveDouble,
14149
14581
  notes
14150
14582
  };
14151
14583
  }) : [];
@@ -14173,7 +14605,7 @@ var mountDAW = (target, options = {}) => {
14173
14605
  }
14174
14606
  if (playbackState !== "paused") {
14175
14607
  const canvas = getGridCanvas();
14176
- currentOffsetX = clamp3(
14608
+ currentOffsetX = clamp4(
14177
14609
  playStartStep * renderConfig.stepWidth - canvas.width * 0.5,
14178
14610
  0,
14179
14611
  getMaxOffsetX()
@@ -14182,6 +14614,29 @@ var mountDAW = (target, options = {}) => {
14182
14614
  }
14183
14615
  playbackState = "playing";
14184
14616
  sequencer.start(fromStep);
14617
+ {
14618
+ const anchor = sequencer.getStartTime();
14619
+ const secondsPerStepFade = 60 / bpm / 48;
14620
+ const params = {};
14621
+ if (fadeInSec > 0 && fromStep === 0) {
14622
+ params.fadeInStartAt = anchor;
14623
+ params.fadeInEndAt = anchor + fadeInSec;
14624
+ }
14625
+ if (fadeOutSec > 0) {
14626
+ const endStep = getSongEndStepExact();
14627
+ const totalDurationSec = (endStep - fromStep) * secondsPerStepFade;
14628
+ if (totalDurationSec > 0) {
14629
+ const fadeOutEndAt = anchor + totalDurationSec;
14630
+ const earliestStart = params.fadeInEndAt ?? anchor;
14631
+ params.fadeOutEndAt = fadeOutEndAt;
14632
+ params.fadeOutStartAt = Math.max(
14633
+ fadeOutEndAt - fadeOutSec,
14634
+ earliestStart
14635
+ );
14636
+ }
14637
+ }
14638
+ options.onScheduleFade?.(params);
14639
+ }
14185
14640
  if (streaming && voices) {
14186
14641
  voices.startStream(streamTracks, sequencer.getStartTime(), {
14187
14642
  isAudible: (t) => !isSolo || t.id === activeTrackId,
@@ -14199,6 +14654,7 @@ var mountDAW = (target, options = {}) => {
14199
14654
  sequencer.stop();
14200
14655
  options.singingVoices?.stopStream();
14201
14656
  clearSoundTimers();
14657
+ options.onScheduleFade?.(null);
14202
14658
  playbackState = "paused";
14203
14659
  updateTransport();
14204
14660
  };
@@ -14212,6 +14668,7 @@ var mountDAW = (target, options = {}) => {
14212
14668
  sequencer.stop();
14213
14669
  options.singingVoices?.stopStream();
14214
14670
  clearSoundTimers();
14671
+ options.onScheduleFade?.(null);
14215
14672
  playbackState = "stopped";
14216
14673
  currentPlayStep = 0;
14217
14674
  updateTransport();
@@ -14253,7 +14710,23 @@ var mountDAW = (target, options = {}) => {
14253
14710
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14254
14711
  </div>
14255
14712
  <details class="dtm-advanced" data-dtm="track-fx-advanced">
14256
- <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14713
+ <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08EQ\u30FB\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14714
+ <div class="dtm-row">
14715
+ <span class="dtm-label">EQ\u4F4E\u57DF</span>
14716
+ <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">
14717
+ <span class="dtm-label" data-dtm="track-eq-low-label"></span>
14718
+ <button class="dtm-infobtn" data-dtm="track-eq-info" title="EQ\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14719
+ </div>
14720
+ <div class="dtm-row">
14721
+ <span class="dtm-label">EQ\u4E2D\u57DF</span>
14722
+ <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">
14723
+ <span class="dtm-label" data-dtm="track-eq-mid-label"></span>
14724
+ </div>
14725
+ <div class="dtm-row">
14726
+ <span class="dtm-label">EQ\u9AD8\u57DF</span>
14727
+ <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">
14728
+ <span class="dtm-label" data-dtm="track-eq-high-label"></span>
14729
+ </div>
14257
14730
  <div class="dtm-row">
14258
14731
  <span class="dtm-label">\u97F3\u5727\u5F37\u5316</span>
14259
14732
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-comp" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u91CF\uFF08\u97F3\u5727\u5F37\u5316\uFF09">
@@ -14283,6 +14756,52 @@ var mountDAW = (target, options = {}) => {
14283
14756
  volInput.title = active.lyricModel ? "\u6B4C\u8A5E\u30E2\u30FC\u30C9\u306E\u3068\u304D\u306F\u30D9\u30ED\u30B7\u30C6\u30A3\u304C\u518D\u751F\u306B\u53CD\u6620\u3055\u308C\u307E\u305B\u3093\uFF08\u58F0\u91CF\u3067\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\uFF09" : "";
14284
14757
  };
14285
14758
  syncVelocityDisabled();
14759
+ const trackEqLowInput = refs.trackBody.querySelector(
14760
+ '[data-dtm="track-eq-low"]'
14761
+ );
14762
+ const trackEqLowLabel = refs.trackBody.querySelector(
14763
+ '[data-dtm="track-eq-low-label"]'
14764
+ );
14765
+ const trackEqMidInput = refs.trackBody.querySelector(
14766
+ '[data-dtm="track-eq-mid"]'
14767
+ );
14768
+ const trackEqMidLabel = refs.trackBody.querySelector(
14769
+ '[data-dtm="track-eq-mid-label"]'
14770
+ );
14771
+ const trackEqHighInput = refs.trackBody.querySelector(
14772
+ '[data-dtm="track-eq-high"]'
14773
+ );
14774
+ const trackEqHighLabel = refs.trackBody.querySelector(
14775
+ '[data-dtm="track-eq-high-label"]'
14776
+ );
14777
+ const trackEqInfo = refs.trackBody.querySelector(
14778
+ '[data-dtm="track-eq-info"]'
14779
+ );
14780
+ const fmtDb = (db) => db > 0 ? `+${db}dB` : `${db}dB`;
14781
+ trackEqLowInput.value = String(active.trackEqLow);
14782
+ trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
14783
+ trackEqMidInput.value = String(active.trackEqMid);
14784
+ trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
14785
+ trackEqHighInput.value = String(active.trackEqHigh);
14786
+ trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
14787
+ trackEqLowInput.addEventListener("input", () => {
14788
+ active.trackEqLow = Number.parseInt(trackEqLowInput.value, 10);
14789
+ trackEqLowLabel.textContent = fmtDb(active.trackEqLow);
14790
+ options.onTrackEqLowChange?.(active.config.id, active.trackEqLow);
14791
+ });
14792
+ trackEqMidInput.addEventListener("input", () => {
14793
+ active.trackEqMid = Number.parseInt(trackEqMidInput.value, 10);
14794
+ trackEqMidLabel.textContent = fmtDb(active.trackEqMid);
14795
+ options.onTrackEqMidChange?.(active.config.id, active.trackEqMid);
14796
+ });
14797
+ trackEqHighInput.addEventListener("input", () => {
14798
+ active.trackEqHigh = Number.parseInt(trackEqHighInput.value, 10);
14799
+ trackEqHighLabel.textContent = fmtDb(active.trackEqHigh);
14800
+ options.onTrackEqHighChange?.(active.config.id, active.trackEqHigh);
14801
+ });
14802
+ trackEqInfo.addEventListener("click", () => {
14803
+ showModal("EQ\uFF08\u30A4\u30B3\u30E9\u30A4\u30B6\u30FC\uFF09\u306E\u89E3\u8AAC", TRACK_EQ_INFO_HTML);
14804
+ });
14286
14805
  const trackCompInput = refs.trackBody.querySelector(
14287
14806
  '[data-dtm="track-comp"]'
14288
14807
  );
@@ -14435,6 +14954,12 @@ var mountDAW = (target, options = {}) => {
14435
14954
  <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14436
14955
  <button class="dtm-infobtn" data-dtm="lyric-reverb-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14437
14956
  </div>
14957
+ <div class="dtm-row">
14958
+ <span class="dtm-label">\u30C7\u30A3\u30EC\u30A4\u9001\u308A</span>
14959
+ <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">
14960
+ <span class="dtm-label" data-dtm="lyric-delay-label"></span>
14961
+ <button class="dtm-infobtn" data-dtm="lyric-delay-info" title="\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14962
+ </div>
14438
14963
  <div class="dtm-row">
14439
14964
  <span class="dtm-label">\u30B8\u30A7\u30F3\u30C0\u30FC</span>
14440
14965
  <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">
@@ -14453,6 +14978,12 @@ var mountDAW = (target, options = {}) => {
14453
14978
  </span>
14454
14979
  <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14455
14980
  </div>
14981
+ <div class="dtm-row">
14982
+ <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14983
+ <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
14984
+ </span>
14985
+ <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>
14986
+ </div>
14456
14987
  </details>
14457
14988
  <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>
14458
14989
  </div>`;
@@ -14499,6 +15030,18 @@ var mountDAW = (target, options = {}) => {
14499
15030
  lyricReverbInfo.addEventListener("click", () => {
14500
15031
  showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_REVERB_INFO_HTML);
14501
15032
  });
15033
+ const lyricDelay = lyricDiv.querySelector(
15034
+ '[data-dtm="lyric-delay"]'
15035
+ );
15036
+ const lyricDelayLabel = lyricDiv.querySelector(
15037
+ '[data-dtm="lyric-delay-label"]'
15038
+ );
15039
+ const lyricDelayInfo = lyricDiv.querySelector(
15040
+ '[data-dtm="lyric-delay-info"]'
15041
+ );
15042
+ lyricDelayInfo.addEventListener("click", () => {
15043
+ showModal("\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_DELAY_INFO_HTML);
15044
+ });
14502
15045
  const lyricGender = lyricDiv.querySelector(
14503
15046
  '[data-dtm="lyric-gender"]'
14504
15047
  );
@@ -14532,6 +15075,15 @@ var mountDAW = (target, options = {}) => {
14532
15075
  lyricVibratoInfo.addEventListener("click", () => {
14533
15076
  showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14534
15077
  });
15078
+ const lyricOctaveDouble = lyricDiv.querySelector(
15079
+ '[data-dtm="lyric-octave-double"]'
15080
+ );
15081
+ const lyricOctaveDoubleInfo = lyricDiv.querySelector(
15082
+ '[data-dtm="lyric-octave-double-info"]'
15083
+ );
15084
+ lyricOctaveDoubleInfo.addEventListener("click", () => {
15085
+ showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u89E3\u8AAC", OCTAVE_DOUBLE_INFO_HTML);
15086
+ });
14535
15087
  const lyricTerms = lyricDiv.querySelector(
14536
15088
  '[data-dtm="lyric-terms"]'
14537
15089
  );
@@ -14595,11 +15147,14 @@ var mountDAW = (target, options = {}) => {
14595
15147
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14596
15148
  lyricReverb.value = String(active.vocalReverb);
14597
15149
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
15150
+ lyricDelay.value = String(active.vocalDelay);
15151
+ lyricDelayLabel.textContent = `${active.vocalDelay}%`;
14598
15152
  lyricGender.value = String(active.vocalGender);
14599
15153
  lyricGenderLabel.textContent = `${active.vocalGender}`;
14600
15154
  lyricBreathiness.value = String(active.vocalBreathiness);
14601
15155
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14602
15156
  lyricVibrato.checked = active.vocalVibrato;
15157
+ lyricOctaveDouble.checked = active.vocalOctaveDouble;
14603
15158
  const updateLyricCount = () => {
14604
15159
  const n = normalizeLyrics(lyricInput.value).length;
14605
15160
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -14721,6 +15276,10 @@ var mountDAW = (target, options = {}) => {
14721
15276
  active.vocalVibrato = lyricVibrato.checked;
14722
15277
  fireLyricsChange(active);
14723
15278
  });
15279
+ lyricOctaveDouble.addEventListener("change", () => {
15280
+ active.vocalOctaveDouble = lyricOctaveDouble.checked;
15281
+ fireLyricsChange(active);
15282
+ });
14724
15283
  lyricInput.addEventListener("input", () => {
14725
15284
  active.lyrics = lyricInput.value;
14726
15285
  updateLyricCount();
@@ -14749,6 +15308,11 @@ var mountDAW = (target, options = {}) => {
14749
15308
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14750
15309
  fireLyricsChange(active);
14751
15310
  });
15311
+ lyricDelay.addEventListener("input", () => {
15312
+ active.vocalDelay = Number.parseInt(lyricDelay.value, 10);
15313
+ lyricDelayLabel.textContent = `${active.vocalDelay}%`;
15314
+ fireLyricsChange(active);
15315
+ });
14752
15316
  lyricGender.addEventListener("input", () => {
14753
15317
  active.vocalGender = Number.parseInt(lyricGender.value, 10);
14754
15318
  lyricGenderLabel.textContent = `${active.vocalGender}`;
@@ -14844,15 +15408,24 @@ var mountDAW = (target, options = {}) => {
14844
15408
  const trackInstrumentsForMeta = {};
14845
15409
  const trackCompressionForMeta = {};
14846
15410
  const trackWidthForMeta = {};
15411
+ const trackEqLowForMeta = {};
15412
+ const trackEqMidForMeta = {};
15413
+ const trackEqHighForMeta = {};
14847
15414
  trackStates.forEach((t, i2) => {
14848
15415
  if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14849
15416
  if (t.trackCompression !== 0)
14850
15417
  trackCompressionForMeta[i2] = t.trackCompression;
14851
15418
  if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
15419
+ if (t.trackEqLow !== 0) trackEqLowForMeta[i2] = t.trackEqLow;
15420
+ if (t.trackEqMid !== 0) trackEqMidForMeta[i2] = t.trackEqMid;
15421
+ if (t.trackEqHigh !== 0) trackEqHighForMeta[i2] = t.trackEqHigh;
14852
15422
  });
14853
15423
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14854
15424
  const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
14855
15425
  const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
15426
+ const trackEqLowMeta = Object.keys(trackEqLowForMeta).length > 0 ? trackEqLowForMeta : void 0;
15427
+ const trackEqMidMeta = Object.keys(trackEqMidForMeta).length > 0 ? trackEqMidForMeta : void 0;
15428
+ const trackEqHighMeta = Object.keys(trackEqHighForMeta).length > 0 ? trackEqHighForMeta : void 0;
14856
15429
  const metaLineFull = formatMmlMeta(
14857
15430
  {
14858
15431
  instrument: currentInstrument || void 0,
@@ -14860,10 +15433,19 @@ var mountDAW = (target, options = {}) => {
14860
15433
  volume: masterVolume,
14861
15434
  drumVolume,
14862
15435
  reverb: reverbAmount,
15436
+ reverbDecay: Math.round(reverbDecay * 10),
15437
+ reverbPreDelay,
15438
+ delay: delayAmount,
15439
+ delayDivision,
15440
+ fadeIn: Math.round(fadeInSec * 10),
15441
+ fadeOut: Math.round(fadeOutSec * 10),
14863
15442
  mode,
14864
15443
  trackInstruments: trackInstMeta,
14865
15444
  trackCompression: trackCompMeta,
14866
- trackWidth: trackWidthMeta
15445
+ trackWidth: trackWidthMeta,
15446
+ trackEqLow: trackEqLowMeta,
15447
+ trackEqMid: trackEqMidMeta,
15448
+ trackEqHigh: trackEqHighMeta
14867
15449
  },
14868
15450
  " "
14869
15451
  );
@@ -14874,10 +15456,19 @@ var mountDAW = (target, options = {}) => {
14874
15456
  volume: masterVolume,
14875
15457
  drumVolume,
14876
15458
  reverb: reverbAmount,
15459
+ reverbDecay: Math.round(reverbDecay * 10),
15460
+ reverbPreDelay,
15461
+ delay: delayAmount,
15462
+ delayDivision,
15463
+ fadeIn: Math.round(fadeInSec * 10),
15464
+ fadeOut: Math.round(fadeOutSec * 10),
14877
15465
  mode,
14878
15466
  trackInstruments: trackInstMeta,
14879
15467
  trackCompression: trackCompMeta,
14880
- trackWidth: trackWidthMeta
15468
+ trackWidth: trackWidthMeta,
15469
+ trackEqLow: trackEqLowMeta,
15470
+ trackEqMid: trackEqMidMeta,
15471
+ trackEqHigh: trackEqHighMeta
14881
15472
  },
14882
15473
  ""
14883
15474
  );
@@ -14927,8 +15518,10 @@ var mountDAW = (target, options = {}) => {
14927
15518
  oct: t.vocalOctave,
14928
15519
  vib: t.vocalVibrato,
14929
15520
  rev: t.vocalReverb,
15521
+ del: t.vocalDelay,
14930
15522
  gen: t.vocalGender,
14931
- bre: t.vocalBreathiness
15523
+ bre: t.vocalBreathiness,
15524
+ dbl: t.vocalOctaveDouble
14932
15525
  })).filter(
14933
15526
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
14934
15527
  ).map((x2) => {
@@ -14939,8 +15532,10 @@ var mountDAW = (target, options = {}) => {
14939
15532
  x2.oct === 0 ? "" : `o${x2.oct}`,
14940
15533
  x2.vib ? "b1" : "",
14941
15534
  x2.rev === 0 ? "" : `r${x2.rev}`,
15535
+ x2.del === 0 ? "" : `e${x2.del}`,
14942
15536
  x2.gen === 50 ? "" : `g${x2.gen}`,
14943
- x2.bre === 50 ? "" : `h${x2.bre}`
15537
+ x2.bre === 50 ? "" : `h${x2.bre}`,
15538
+ x2.dbl ? "w1" : ""
14944
15539
  ].filter((s) => s.length > 0).join(" ");
14945
15540
  const head = params ? `${x2.model} ${params}` : x2.model;
14946
15541
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -15009,7 +15604,7 @@ var mountDAW = (target, options = {}) => {
15009
15604
  const canvas = getGridCanvas();
15010
15605
  const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
15011
15606
  const logicalY = yIndex * renderConfig.keyHeight;
15012
- currentOffsetY = clamp3(
15607
+ currentOffsetY = clamp4(
15013
15608
  logicalY - (canvas.height - renderConfig.keyHeight) / 2,
15014
15609
  0,
15015
15610
  getMaxOffsetY()
@@ -15086,6 +15681,39 @@ var mountDAW = (target, options = {}) => {
15086
15681
  refs.reverbAmountLabel.textContent = `${meta.reverb}%`;
15087
15682
  options.onReverbChange?.(meta.reverb);
15088
15683
  }
15684
+ if (meta.reverbDecay !== void 0) {
15685
+ reverbDecay = meta.reverbDecay / 10;
15686
+ refs.reverbDecay.value = String(meta.reverbDecay);
15687
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
15688
+ options.onReverbDecayChange?.(reverbDecay);
15689
+ }
15690
+ if (meta.reverbPreDelay !== void 0) {
15691
+ reverbPreDelay = meta.reverbPreDelay;
15692
+ refs.reverbPreDelay.value = String(meta.reverbPreDelay);
15693
+ refs.reverbPreDelayLabel.textContent = `${meta.reverbPreDelay}ms`;
15694
+ options.onReverbPreDelayChange?.(reverbPreDelay);
15695
+ }
15696
+ if (meta.delay !== void 0) {
15697
+ delayAmount = meta.delay;
15698
+ refs.delayAmount.value = String(meta.delay);
15699
+ refs.delayAmountLabel.textContent = `${meta.delay}%`;
15700
+ options.onDelayChange?.(meta.delay);
15701
+ }
15702
+ if (meta.delayDivision && ["4", "8", "8d", "16"].includes(meta.delayDivision)) {
15703
+ delayDivision = meta.delayDivision;
15704
+ refs.delayDivision.value = delayDivision;
15705
+ options.onDelayDivisionChange?.(delayDivision);
15706
+ }
15707
+ if (meta.fadeIn !== void 0) {
15708
+ fadeInSec = meta.fadeIn / 10;
15709
+ refs.fadeIn.value = String(fadeInSec);
15710
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
15711
+ }
15712
+ if (meta.fadeOut !== void 0) {
15713
+ fadeOutSec = meta.fadeOut / 10;
15714
+ refs.fadeOut.value = String(fadeOutSec);
15715
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
15716
+ }
15089
15717
  }
15090
15718
  trackStates.forEach((t, i2) => {
15091
15719
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -15107,6 +15735,21 @@ var mountDAW = (target, options = {}) => {
15107
15735
  t.trackWidth = width;
15108
15736
  options.onTrackWidthChange?.(t.config.id, width);
15109
15737
  }
15738
+ const eqLow = meta.trackEqLow?.[i2] ?? 0;
15739
+ if (t.trackEqLow !== eqLow) {
15740
+ t.trackEqLow = eqLow;
15741
+ options.onTrackEqLowChange?.(t.config.id, eqLow);
15742
+ }
15743
+ const eqMid = meta.trackEqMid?.[i2] ?? 0;
15744
+ if (t.trackEqMid !== eqMid) {
15745
+ t.trackEqMid = eqMid;
15746
+ options.onTrackEqMidChange?.(t.config.id, eqMid);
15747
+ }
15748
+ const eqHigh = meta.trackEqHigh?.[i2] ?? 0;
15749
+ if (t.trackEqHigh !== eqHigh) {
15750
+ t.trackEqHigh = eqHigh;
15751
+ options.onTrackEqHighChange?.(t.config.id, eqHigh);
15752
+ }
15110
15753
  });
15111
15754
  trackStates.forEach((t, i2) => {
15112
15755
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -15126,8 +15769,10 @@ var mountDAW = (target, options = {}) => {
15126
15769
  active.vocalOctave = 0;
15127
15770
  active.vocalVibrato = false;
15128
15771
  active.vocalReverb = 0;
15772
+ active.vocalDelay = 0;
15129
15773
  active.vocalGender = 50;
15130
15774
  active.vocalBreathiness = 50;
15775
+ active.vocalOctaveDouble = false;
15131
15776
  } else {
15132
15777
  for (const t of trackStates) {
15133
15778
  t.lyrics = "";
@@ -15138,8 +15783,10 @@ var mountDAW = (target, options = {}) => {
15138
15783
  t.vocalOctave = 0;
15139
15784
  t.vocalVibrato = false;
15140
15785
  t.vocalReverb = 0;
15786
+ t.vocalDelay = 0;
15141
15787
  t.vocalGender = 50;
15142
15788
  t.vocalBreathiness = 50;
15789
+ t.vocalOctaveDouble = false;
15143
15790
  }
15144
15791
  }
15145
15792
  lyrics?.forEach((lt) => {
@@ -15154,8 +15801,10 @@ var mountDAW = (target, options = {}) => {
15154
15801
  t.vocalOctave = lt.octave ?? 0;
15155
15802
  t.vocalVibrato = lt.vibrato ?? false;
15156
15803
  t.vocalReverb = lt.reverb ?? 0;
15804
+ t.vocalDelay = lt.delay ?? 0;
15157
15805
  t.vocalGender = lt.gender ?? 50;
15158
15806
  t.vocalBreathiness = lt.breathiness ?? 50;
15807
+ t.vocalOctaveDouble = lt.octaveDouble ?? false;
15159
15808
  });
15160
15809
  for (const p of placements) {
15161
15810
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -15236,8 +15885,10 @@ var mountDAW = (target, options = {}) => {
15236
15885
  active.vocalOctave = 0;
15237
15886
  active.vocalVibrato = false;
15238
15887
  active.vocalReverb = 0;
15888
+ active.vocalDelay = 0;
15239
15889
  active.vocalGender = 50;
15240
15890
  active.vocalBreathiness = 50;
15891
+ active.vocalOctaveDouble = false;
15241
15892
  } else {
15242
15893
  clearAll();
15243
15894
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -15250,8 +15901,10 @@ var mountDAW = (target, options = {}) => {
15250
15901
  t.vocalOctave = 0;
15251
15902
  t.vocalVibrato = false;
15252
15903
  t.vocalReverb = 0;
15904
+ t.vocalDelay = 0;
15253
15905
  t.vocalGender = 50;
15254
15906
  t.vocalBreathiness = 50;
15907
+ t.vocalOctaveDouble = false;
15255
15908
  }
15256
15909
  }
15257
15910
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -15300,6 +15953,7 @@ var mountDAW = (target, options = {}) => {
15300
15953
  bpm = value;
15301
15954
  refs.bpmInput.value = String(value);
15302
15955
  for (const t of trackStates) t.core.setTempo(value);
15956
+ options.onBpmChange?.(value);
15303
15957
  };
15304
15958
  let lastUndoTime = 0;
15305
15959
  const undo = () => {
@@ -15475,6 +16129,43 @@ var mountDAW = (target, options = {}) => {
15475
16129
  refs.reverbAmountInfoBtn.addEventListener("click", () => {
15476
16130
  showModal("\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC", MASTER_REVERB_INFO_HTML);
15477
16131
  });
16132
+ refs.reverbDecay.addEventListener("input", () => {
16133
+ const raw = Number.parseInt(refs.reverbDecay.value, 10) || 22;
16134
+ reverbDecay = Math.max(
16135
+ MIN_REVERB_DECAY_SEC,
16136
+ Math.min(MAX_REVERB_DECAY_SEC, raw / 10)
16137
+ );
16138
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
16139
+ options.onReverbDecayChange?.(reverbDecay);
16140
+ });
16141
+ refs.reverbPreDelay.addEventListener("input", () => {
16142
+ reverbPreDelay = Number.parseInt(refs.reverbPreDelay.value, 10) || 0;
16143
+ refs.reverbPreDelayLabel.textContent = `${reverbPreDelay}ms`;
16144
+ options.onReverbPreDelayChange?.(reverbPreDelay);
16145
+ });
16146
+ refs.delayAmount.addEventListener("input", () => {
16147
+ delayAmount = Number.parseInt(refs.delayAmount.value, 10) || 0;
16148
+ refs.delayAmountLabel.textContent = `${delayAmount}%`;
16149
+ options.onDelayChange?.(delayAmount);
16150
+ });
16151
+ refs.delayDivision.addEventListener("change", () => {
16152
+ delayDivision = refs.delayDivision.value;
16153
+ options.onDelayDivisionChange?.(delayDivision);
16154
+ });
16155
+ refs.delayAmountInfoBtn.addEventListener("click", () => {
16156
+ showModal("\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC", MASTER_DELAY_INFO_HTML);
16157
+ });
16158
+ refs.fadeIn.addEventListener("input", () => {
16159
+ fadeInSec = Number.parseFloat(refs.fadeIn.value) || 0;
16160
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
16161
+ });
16162
+ refs.fadeOut.addEventListener("input", () => {
16163
+ fadeOutSec = Number.parseFloat(refs.fadeOut.value) || 0;
16164
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
16165
+ });
16166
+ refs.fadeInfoBtn.addEventListener("click", () => {
16167
+ showModal("\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC", FADE_INFO_HTML);
16168
+ });
15478
16169
  refs.autoMasterInfoBtn.addEventListener("click", () => {
15479
16170
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15480
16171
  });
@@ -15483,14 +16174,39 @@ var mountDAW = (target, options = {}) => {
15483
16174
  refs.reverbAmount.value = "20";
15484
16175
  refs.reverbAmountLabel.textContent = "20%";
15485
16176
  options.onReverbChange?.(20);
16177
+ const bpmT = clamp4((bpm - 60) / (180 - 60), 0, 1);
16178
+ reverbDecay = clamp4(
16179
+ 3 - bpmT * (3 - 0.9),
16180
+ MIN_REVERB_DECAY_SEC,
16181
+ MAX_REVERB_DECAY_SEC
16182
+ );
16183
+ refs.reverbDecay.value = String(Math.round(reverbDecay * 10));
16184
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
16185
+ options.onReverbDecayChange?.(reverbDecay);
16186
+ reverbPreDelay = 20;
16187
+ refs.reverbPreDelay.value = "20";
16188
+ refs.reverbPreDelayLabel.textContent = "20ms";
16189
+ options.onReverbPreDelayChange?.(20);
16190
+ let mainVocalId = null;
16191
+ let mainVocalScore = -1;
16192
+ for (const t of trackStates) {
16193
+ if (!t.lyricModel) continue;
16194
+ const score = t.core.getNotes().reduce((sum, n) => sum + n.durationSteps, 0);
16195
+ if (score > mainVocalScore) {
16196
+ mainVocalScore = score;
16197
+ mainVocalId = t.config.id;
16198
+ }
16199
+ }
15486
16200
  for (const t of trackStates) {
15487
- t.trackCompression = 35;
16201
+ const isMainVocal = !!t.lyricModel && t.config.id === mainVocalId;
16202
+ const comp = !t.lyricModel ? 35 : isMainVocal ? 30 : 25;
16203
+ t.trackCompression = comp;
15488
16204
  t.trackWidth = 115;
15489
- options.onTrackCompressionChange?.(t.config.id, 35);
16205
+ options.onTrackCompressionChange?.(t.config.id, comp);
15490
16206
  options.onTrackWidthChange?.(t.config.id, 115);
15491
16207
  if (t.lyricModel) {
15492
16208
  t.vocalVibrato = true;
15493
- t.vocalReverb = 25;
16209
+ t.vocalReverb = isMainVocal ? 15 : 30;
15494
16210
  fireLyricsChange(t);
15495
16211
  }
15496
16212
  }
@@ -15727,6 +16443,20 @@ var mountDAW = (target, options = {}) => {
15727
16443
  redrawAll();
15728
16444
  })
15729
16445
  );
16446
+ refs.transposeApplyBtn.addEventListener(
16447
+ "click",
16448
+ () => overlayDuring(() => {
16449
+ transposeNotes(
16450
+ trackStates.map((t) => t.core),
16451
+ Number.parseInt(refs.transposeSelect.value, 10) || 0
16452
+ );
16453
+ redrawAll();
16454
+ updateUndoRedo();
16455
+ })
16456
+ );
16457
+ refs.transposeInfoBtn.addEventListener("click", () => {
16458
+ showModal("\u79FB\u8ABF\u306E\u89E3\u8AAC", TRANSPOSE_INFO_HTML);
16459
+ });
15730
16460
  if (showMidi) wireMidi();
15731
16461
  if (showMidiSearch) wireMidiSearch();
15732
16462
  document.addEventListener("keydown", onKeyDown);
@@ -16372,7 +17102,7 @@ var mountDAW = (target, options = {}) => {
16372
17102
  currentPlayStep = step;
16373
17103
  playbackState = "paused";
16374
17104
  const canvas = getGridCanvas();
16375
- currentOffsetX = clamp3(
17105
+ currentOffsetX = clamp4(
16376
17106
  step * renderConfig.stepWidth - canvas.width * 0.5,
16377
17107
  0,
16378
17108
  getMaxOffsetX()
@@ -16418,11 +17148,11 @@ var mountDAW = (target, options = {}) => {
16418
17148
  getViewState,
16419
17149
  setViewState: (state) => {
16420
17150
  if (typeof state.zoomX === "number") {
16421
- zoomX = clamp3(state.zoomX, 25, 200);
17151
+ zoomX = clamp4(state.zoomX, 25, 200);
16422
17152
  applyZoomX();
16423
17153
  }
16424
17154
  if (typeof state.zoomY === "number") {
16425
- zoomY = clamp3(state.zoomY, 50, 200);
17155
+ zoomY = clamp4(state.zoomY, 50, 200);
16426
17156
  applyZoomY();
16427
17157
  }
16428
17158
  if (typeof state.decomposeChord === "boolean") {
@@ -16444,28 +17174,52 @@ var mountDAW = (target, options = {}) => {
16444
17174
  forcePauseAt,
16445
17175
  setLoading,
16446
17176
  setMasterVolume: (volume) => {
16447
- masterVolume = clamp3(volume, 0, 100);
17177
+ masterVolume = clamp4(volume, 0, 100);
16448
17178
  refs.masterVolume.value = String(masterVolume);
16449
17179
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
16450
17180
  options.singingVoices?.setVolume(masterVolume / 100);
16451
17181
  },
16452
17182
  setVolume: (volume) => {
16453
- masterVolume = clamp3(volume, 0, 100);
17183
+ masterVolume = clamp4(volume, 0, 100);
16454
17184
  refs.masterVolume.value = String(masterVolume);
16455
17185
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
16456
17186
  options.singingVoices?.setVolume(masterVolume / 100);
16457
17187
  },
16458
17188
  setDrumVolume: (volume) => {
16459
- drumVolume = clamp3(volume, 0, 100);
17189
+ drumVolume = clamp4(volume, 0, 100);
16460
17190
  refs.drumVolume.value = String(drumVolume);
16461
17191
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
16462
17192
  },
16463
17193
  setReverbAmount: (amount) => {
16464
- reverbAmount = clamp3(amount, 0, 100);
17194
+ reverbAmount = clamp4(amount, 0, 100);
16465
17195
  refs.reverbAmount.value = String(reverbAmount);
16466
17196
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
16467
17197
  options.onReverbChange?.(reverbAmount);
16468
17198
  },
17199
+ setReverbDecay: (seconds) => {
17200
+ reverbDecay = Math.max(
17201
+ MIN_REVERB_DECAY_SEC,
17202
+ Math.min(MAX_REVERB_DECAY_SEC, seconds)
17203
+ );
17204
+ refs.reverbDecay.value = String(Math.round(reverbDecay * 10));
17205
+ refs.reverbDecayLabel.textContent = `${reverbDecay.toFixed(1)}s`;
17206
+ options.onReverbDecayChange?.(reverbDecay);
17207
+ },
17208
+ setReverbPreDelay: (ms) => {
17209
+ reverbPreDelay = Math.max(
17210
+ MIN_REVERB_PREDELAY_MS,
17211
+ Math.min(MAX_REVERB_PREDELAY_MS, ms)
17212
+ );
17213
+ refs.reverbPreDelay.value = String(reverbPreDelay);
17214
+ refs.reverbPreDelayLabel.textContent = `${reverbPreDelay}ms`;
17215
+ options.onReverbPreDelayChange?.(reverbPreDelay);
17216
+ },
17217
+ setDelayAmount: (amount) => {
17218
+ delayAmount = clamp4(amount, 0, 100);
17219
+ refs.delayAmount.value = String(delayAmount);
17220
+ refs.delayAmountLabel.textContent = `${delayAmount}%`;
17221
+ options.onDelayChange?.(delayAmount);
17222
+ },
16469
17223
  applyPatch: (trackId, added, removed) => {
16470
17224
  const track = trackStates.find((t) => t.config.id === trackId);
16471
17225
  if (!track) return;
@@ -16507,8 +17261,10 @@ var mountDAW = (target, options = {}) => {
16507
17261
  t.vocalOctave = data.vocalOctave;
16508
17262
  t.vocalVibrato = data.vocalVibrato ?? false;
16509
17263
  t.vocalReverb = data.vocalReverb ?? 0;
17264
+ t.vocalDelay = data.vocalDelay ?? 0;
16510
17265
  t.vocalGender = data.vocalGender ?? 50;
16511
17266
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17267
+ t.vocalOctaveDouble = data.vocalOctaveDouble ?? false;
16512
17268
  },
16513
17269
  applyTrackInstrument: (trackIndex, instrumentName) => {
16514
17270
  const t = trackStates[trackIndex];
@@ -16627,8 +17383,10 @@ var playSingingMML = async (mml, options = {}) => {
16627
17383
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16628
17384
  vibrato: lt.vibrato,
16629
17385
  reverbSend: (lt.reverb ?? 0) / 100,
17386
+ delaySend: (lt.delay ?? 0) / 100,
16630
17387
  gender: (lt.gender ?? 50) / 100,
16631
17388
  breathiness: (lt.breathiness ?? 50) / 100,
17389
+ octaveDouble: lt.octaveDouble,
16632
17390
  notes
16633
17391
  };
16634
17392
  });
@@ -17237,9 +17995,9 @@ var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
17237
17995
  URL.revokeObjectURL(url);
17238
17996
 
17239
17997
  // src/channel-strip.ts
17240
- var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17998
+ var clamp5 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17241
17999
  var compressionParams = (amount) => {
17242
- const t = clamp4(amount, 0, 100) / 100;
18000
+ const t = clamp5(amount, 0, 100) / 100;
17243
18001
  return {
17244
18002
  threshold: 0 + (-24 - 0) * t,
17245
18003
  ratio: 1 + (12 - 1) * t,
@@ -17248,8 +18006,29 @@ var compressionParams = (amount) => {
17248
18006
  release: 0.25 + (0.15 - 0.25) * t
17249
18007
  };
17250
18008
  };
18009
+ var EQ_LOW_FREQ = 200;
18010
+ var EQ_MID_FREQ = 1e3;
18011
+ var EQ_HIGH_FREQ = 5e3;
18012
+ var EQ_MID_Q = 1;
18013
+ var EQ_MAX_DB = 12;
17251
18014
  var createChannelStrip = (ctx, destination, options = {}) => {
17252
18015
  const input = ctx.createGain();
18016
+ const eqLow = ctx.createBiquadFilter();
18017
+ eqLow.type = "lowshelf";
18018
+ eqLow.frequency.value = EQ_LOW_FREQ;
18019
+ eqLow.gain.value = clamp5(options.eqLow ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18020
+ const eqMid = ctx.createBiquadFilter();
18021
+ eqMid.type = "peaking";
18022
+ eqMid.frequency.value = EQ_MID_FREQ;
18023
+ eqMid.Q.value = EQ_MID_Q;
18024
+ eqMid.gain.value = clamp5(options.eqMid ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18025
+ const eqHigh = ctx.createBiquadFilter();
18026
+ eqHigh.type = "highshelf";
18027
+ eqHigh.frequency.value = EQ_HIGH_FREQ;
18028
+ eqHigh.gain.value = clamp5(options.eqHigh ?? 0, -EQ_MAX_DB, EQ_MAX_DB);
18029
+ input.connect(eqLow);
18030
+ eqLow.connect(eqMid);
18031
+ eqMid.connect(eqHigh);
17253
18032
  const compressor = ctx.createDynamicsCompressor();
17254
18033
  const applyCompression = (amount) => {
17255
18034
  const p = compressionParams(amount);
@@ -17292,21 +18071,45 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17292
18071
  outR.connect(merger, 0, 1);
17293
18072
  const setWidth = (width) => {
17294
18073
  widthGain.gain.setTargetAtTime(
17295
- clamp4(width, 0, 200) / 100,
18074
+ clamp5(width, 0, 200) / 100,
17296
18075
  ctx.currentTime,
17297
18076
  0.02
17298
18077
  );
17299
18078
  };
17300
18079
  setWidth(options.width ?? 100);
17301
- input.connect(compressor);
18080
+ eqHigh.connect(compressor);
17302
18081
  compressor.connect(splitter);
17303
18082
  merger.connect(destination);
17304
18083
  return {
17305
18084
  input,
18085
+ setEqLow: (db) => {
18086
+ eqLow.gain.setTargetAtTime(
18087
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18088
+ ctx.currentTime,
18089
+ 0.02
18090
+ );
18091
+ },
18092
+ setEqMid: (db) => {
18093
+ eqMid.gain.setTargetAtTime(
18094
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18095
+ ctx.currentTime,
18096
+ 0.02
18097
+ );
18098
+ },
18099
+ setEqHigh: (db) => {
18100
+ eqHigh.gain.setTargetAtTime(
18101
+ clamp5(db, -EQ_MAX_DB, EQ_MAX_DB),
18102
+ ctx.currentTime,
18103
+ 0.02
18104
+ );
18105
+ },
17306
18106
  setCompression: applyCompression,
17307
18107
  setWidth,
17308
18108
  dispose: () => {
17309
18109
  input.disconnect();
18110
+ eqLow.disconnect();
18111
+ eqMid.disconnect();
18112
+ eqHigh.disconnect();
17310
18113
  compressor.disconnect();
17311
18114
  splitter.disconnect();
17312
18115
  mid.disconnect();
@@ -17377,25 +18180,6 @@ var createClipMeter = (ctx, source, options = {}) => {
17377
18180
  };
17378
18181
  };
17379
18182
 
17380
- // src/reverb.ts
17381
- var IMPULSE_DURATION_SEC = 2.2;
17382
- var IMPULSE_DECAY = 2.5;
17383
- var createReverbImpulse = (ctx) => {
17384
- const rate = ctx.sampleRate;
17385
- const length = Math.max(1, Math.floor(rate * IMPULSE_DURATION_SEC));
17386
- const impulse = ctx.createBuffer(2, length, rate);
17387
- for (let ch = 0; ch < impulse.numberOfChannels; ch++) {
17388
- const data = impulse.getChannelData(ch);
17389
- for (let i2 = 0; i2 < length; i2++) {
17390
- const envelope = (1 - i2 / length) ** IMPULSE_DECAY;
17391
- data[i2] = (Math.random() * 2 - 1) * envelope;
17392
- }
17393
- }
17394
- return impulse;
17395
- };
17396
- var REVERB_MAX_WET = 0.6;
17397
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
17398
-
17399
18183
  // src/sf/SoundFont_drum.ts
17400
18184
  var touch = (map, key, ctor) => {
17401
18185
  if (!map.has(key)) map.set(key, new ctor());
@@ -17551,12 +18335,16 @@ var createDtmStudio = async (options = {}) => {
17551
18335
  const drumGain = audioCtx.createGain();
17552
18336
  drumGain.gain.value = options.drumVolume ?? 1;
17553
18337
  drumGain.connect(masterGain);
18338
+ let reverbDecaySec = options.reverbDecay ?? DEFAULT_REVERB_DECAY_SEC;
18339
+ const reverbPreDelay = audioCtx.createDelay(MAX_REVERB_PREDELAY_MS / 1e3);
18340
+ reverbPreDelay.delayTime.value = (options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS) / 1e3;
17554
18341
  const reverbConvolver = audioCtx.createConvolver();
17555
- reverbConvolver.buffer = createReverbImpulse(audioCtx);
18342
+ reverbConvolver.buffer = createReverbImpulse(audioCtx, reverbDecaySec);
17556
18343
  reverbConvolver.normalize = true;
17557
18344
  const reverbWetGain = audioCtx.createGain();
17558
18345
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
17559
- masterGain.connect(reverbConvolver);
18346
+ masterGain.connect(reverbPreDelay);
18347
+ reverbPreDelay.connect(reverbConvolver);
17560
18348
  reverbConvolver.connect(reverbWetGain);
17561
18349
  const setReverbAmount = (amount) => {
17562
18350
  reverbWetGain.gain.setTargetAtTime(
@@ -17565,9 +18353,28 @@ var createDtmStudio = async (options = {}) => {
17565
18353
  0.02
17566
18354
  );
17567
18355
  };
18356
+ const setReverbDecay = (seconds) => {
18357
+ reverbDecaySec = Math.max(
18358
+ MIN_REVERB_DECAY_SEC,
18359
+ Math.min(MAX_REVERB_DECAY_SEC, seconds)
18360
+ );
18361
+ reverbConvolver.buffer = createReverbImpulse(audioCtx, reverbDecaySec);
18362
+ };
18363
+ const setReverbPreDelay = (ms) => {
18364
+ reverbPreDelay.delayTime.setTargetAtTime(
18365
+ Math.max(MIN_REVERB_PREDELAY_MS, Math.min(MAX_REVERB_PREDELAY_MS, ms)) / 1e3,
18366
+ audioCtx.currentTime,
18367
+ 0.02
18368
+ );
18369
+ };
17568
18370
  const finalMix = audioCtx.createGain();
17569
18371
  masterGain.connect(finalMix);
17570
18372
  reverbWetGain.connect(finalMix);
18373
+ const delayBus = createDelayBus(audioCtx, finalMix, {
18374
+ amount: options.delayAmount ?? 0,
18375
+ division: options.delayDivision ?? "8"
18376
+ });
18377
+ const setDelayAmount = (amount) => delayBus.setAmount(amount);
17571
18378
  const safetyLimiter = audioCtx.createDynamicsCompressor();
17572
18379
  safetyLimiter.threshold.value = -1;
17573
18380
  safetyLimiter.knee.value = 0;
@@ -17575,7 +18382,28 @@ var createDtmStudio = async (options = {}) => {
17575
18382
  safetyLimiter.attack.value = 1e-3;
17576
18383
  safetyLimiter.release.value = 0.1;
17577
18384
  finalMix.connect(safetyLimiter);
17578
- safetyLimiter.connect(options.destination ?? audioCtx.destination);
18385
+ const fadeGain = audioCtx.createGain();
18386
+ fadeGain.gain.value = 1;
18387
+ safetyLimiter.connect(fadeGain);
18388
+ fadeGain.connect(options.destination ?? audioCtx.destination);
18389
+ const scheduleFade = (params) => {
18390
+ fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
18391
+ if (!params) {
18392
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18393
+ return;
18394
+ }
18395
+ const { fadeInStartAt, fadeInEndAt, fadeOutStartAt, fadeOutEndAt } = params;
18396
+ if (fadeInStartAt !== void 0 && fadeInEndAt !== void 0) {
18397
+ fadeGain.gain.setValueAtTime(0, fadeInStartAt);
18398
+ fadeGain.gain.linearRampToValueAtTime(1, fadeInEndAt);
18399
+ } else {
18400
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18401
+ }
18402
+ if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
18403
+ fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
18404
+ fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
18405
+ }
18406
+ };
17579
18407
  const clipMeter = createClipMeter(audioCtx, finalMix);
17580
18408
  const channelStrips = /* @__PURE__ */ new Map();
17581
18409
  const getChannelStrip = (trackId) => {
@@ -17620,6 +18448,8 @@ var createDtmStudio = async (options = {}) => {
17620
18448
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
17621
18449
  // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
17622
18450
  reverbBus: reverbConvolver,
18451
+ // ボーカルトラック個別のディレイセンド(`e`トークン)先。
18452
+ delayBus: delayBus.input,
17623
18453
  // トラック単位チャンネルストリップ(コンプレッサー/ステレオワイド)の入口。
17624
18454
  // 楽器と同じ getChannelStrip キャッシュを共有するので、演奏トラックと歌詞トラックが
17625
18455
  // 同じ trackId を指していれば同じ処理が掛かる。
@@ -17900,8 +18730,23 @@ var createDtmStudio = async (options = {}) => {
17900
18730
  },
17901
18731
  reverbAmount: options.reverbAmount,
17902
18732
  onReverbChange: setReverbAmount,
18733
+ reverbDecay: options.reverbDecay,
18734
+ onReverbDecayChange: setReverbDecay,
18735
+ reverbPreDelay: options.reverbPreDelay,
18736
+ onReverbPreDelayChange: setReverbPreDelay,
18737
+ delayAmount: options.delayAmount,
18738
+ onDelayChange: setDelayAmount,
18739
+ delayDivision: options.delayDivision,
18740
+ onDelayDivisionChange: (division) => delayBus.setDivision(division),
18741
+ onBpmChange: (bpm) => delayBus.setBpm(bpm),
18742
+ fadeInSec: options.fadeInSec,
18743
+ fadeOutSec: options.fadeOutSec,
18744
+ onScheduleFade: scheduleFade,
17903
18745
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
17904
18746
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18747
+ onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
18748
+ onTrackEqMidChange: (trackId, db) => getChannelStrip(trackId).setEqMid(db),
18749
+ onTrackEqHighChange: (trackId, db) => getChannelStrip(trackId).setEqHigh(db),
17905
18750
  clipMeter,
17906
18751
  ...dawOverrides,
17907
18752
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
@@ -18438,6 +19283,7 @@ var createDtmStudio = async (options = {}) => {
18438
19283
  mountedChordPlayers.length = 0;
18439
19284
  mountedEditors.length = 0;
18440
19285
  clipMeter.dispose();
19286
+ delayBus.dispose();
18441
19287
  for (const strip of channelStrips.values()) strip.dispose();
18442
19288
  channelStrips.clear();
18443
19289
  void audioCtx.close();
@@ -18479,6 +19325,9 @@ var createDtmStudio = async (options = {}) => {
18479
19325
  setMasterVolume,
18480
19326
  setVolume: setMasterVolume,
18481
19327
  setReverbAmount,
19328
+ setReverbDecay,
19329
+ setReverbPreDelay,
19330
+ setDelayAmount,
18482
19331
  dispose
18483
19332
  };
18484
19333
  };
@@ -18588,5 +19437,6 @@ var createDtmStudio = async (options = {}) => {
18588
19437
  stripCustomVocals,
18589
19438
  stripLyrics,
18590
19439
  stripMmlMeta,
19440
+ transposeNotes,
18591
19441
  vocalVolumeToGain
18592
19442
  });