@onjmin/dtm 0.1.86 → 0.1.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2189,8 +2189,9 @@ var parseLyrics = (mml) => {
2189
2189
  let delay = 0;
2190
2190
  let gender = 50;
2191
2191
  let breathiness = 50;
2192
+ let octaveUnison = "none";
2192
2193
  const modelMatch = rest.match(
2193
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghe]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2194
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2194
2195
  );
2195
2196
  let model = "";
2196
2197
  const metaTokens = [];
@@ -2266,6 +2267,13 @@ var parseLyrics = (mml) => {
2266
2267
  rest = rest.substring(eMatch[0].length).trim();
2267
2268
  continue;
2268
2269
  }
2270
+ const wMatch = rest.match(/^w([0-3])/i);
2271
+ if (wMatch) {
2272
+ octaveUnison = ["none", "down", "up", "both"][Number.parseInt(wMatch[1], 10)];
2273
+ metaTokens.push(wMatch[0]);
2274
+ rest = rest.substring(wMatch[0].length).trim();
2275
+ continue;
2276
+ }
2269
2277
  break;
2270
2278
  }
2271
2279
  const lyricLines = [rest];
@@ -2285,6 +2293,7 @@ var parseLyrics = (mml) => {
2285
2293
  delay,
2286
2294
  gender,
2287
2295
  breathiness,
2296
+ octaveUnison,
2288
2297
  syllables,
2289
2298
  metaText: metaTokens.join(" "),
2290
2299
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2881,6 +2890,19 @@ var createKoeVoice = async (ctx, destination, options) => {
2881
2890
  var PREWARM_NOTES = 3;
2882
2891
  var STREAM_LOOKAHEAD_SEC = 1.5;
2883
2892
  var STREAM_POLL_MS = 100;
2893
+ var OCTAVE_UNISON_PEAK_SCALE = 0.6;
2894
+ var octaveUnisonOffsets = (mode) => {
2895
+ switch (mode) {
2896
+ case "down":
2897
+ return [-12];
2898
+ case "up":
2899
+ return [12];
2900
+ case "both":
2901
+ return [-12, 12];
2902
+ default:
2903
+ return [];
2904
+ }
2905
+ };
2884
2906
  var FALLBACK_MODEL = "klatt";
2885
2907
  var createSingingVoices = (ctx, destination, options = {}) => {
2886
2908
  const catalog = {};
@@ -2967,7 +2989,24 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2967
2989
  forEachSungNote(track, (note, prevVowel) => {
2968
2990
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2969
2991
  n++;
2970
- tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
2992
+ tasks.push({
2993
+ model: m,
2994
+ note,
2995
+ prevVowel,
2996
+ pitch: note.pitch,
2997
+ vibrato: track.vibrato,
2998
+ expr
2999
+ });
3000
+ for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
3001
+ tasks.push({
3002
+ model: m,
3003
+ note,
3004
+ prevVowel,
3005
+ pitch: note.pitch + offset,
3006
+ vibrato: track.vibrato,
3007
+ expr
3008
+ });
3009
+ }
2971
3010
  });
2972
3011
  }
2973
3012
  const total = tasks.length;
@@ -2981,7 +3020,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2981
3020
  await (task.model.renderToCache?.(
2982
3021
  task.note.syllable,
2983
3022
  task.prevVowel,
2984
- task.note.pitch,
3023
+ task.pitch,
2985
3024
  task.note.durationSec * 1e3,
2986
3025
  task.vibrato,
2987
3026
  task.expr
@@ -3021,59 +3060,67 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3021
3060
  }
3022
3061
  if (opts?.isAudible && !opts.isAudible(track)) continue;
3023
3062
  const t0 = anchorTime + startSec;
3024
- if (model.renderToCache && model.scheduleCached) {
3025
- const renderToCache = model.renderToCache;
3026
- const scheduleCached = model.scheduleCached;
3027
- void (async () => {
3028
- const key = await renderToCache(
3029
- note.syllable,
3030
- prevVowel,
3031
- note.pitch,
3032
- note.durationSec * 1e3,
3033
- track.vibrato,
3034
- { gender: track.gender, breathiness: track.breathiness }
3035
- );
3036
- if (session !== streamSession) return;
3037
- if (key) {
3038
- const delay = ctx.currentTime - t0;
3039
- if (delay < 0.05) {
3040
- const dest = options.getTrackDestination?.(track.id ?? "");
3041
- const effPeak = dest ? peak * masterVolumeScalar : peak;
3042
- scheduleCached(
3043
- key,
3044
- t0,
3045
- effPeak,
3046
- track.pan,
3047
- track.reverbSend,
3048
- track.delaySend,
3049
- dest
3050
- );
3051
- opts?.onScheduled?.(track, note, t0);
3052
- } else {
3053
- console.warn(
3054
- `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3055
- );
3056
- opts?.onLateSkip?.(note, delay);
3063
+ const dispatchNote = (pitch, peakScale) => {
3064
+ if (model.renderToCache && model.scheduleCached) {
3065
+ const renderToCache = model.renderToCache;
3066
+ const scheduleCached = model.scheduleCached;
3067
+ void (async () => {
3068
+ const key = await renderToCache(
3069
+ note.syllable,
3070
+ prevVowel,
3071
+ pitch,
3072
+ note.durationSec * 1e3,
3073
+ track.vibrato,
3074
+ { gender: track.gender, breathiness: track.breathiness }
3075
+ );
3076
+ if (session !== streamSession) return;
3077
+ if (key) {
3078
+ const delay = ctx.currentTime - t0;
3079
+ if (delay < 0.05) {
3080
+ const dest = options.getTrackDestination?.(track.id ?? "");
3081
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3082
+ scheduleCached(
3083
+ key,
3084
+ t0,
3085
+ effPeak,
3086
+ track.pan,
3087
+ track.reverbSend,
3088
+ track.delaySend,
3089
+ dest
3090
+ );
3091
+ opts?.onScheduled?.(track, note, t0);
3092
+ } else {
3093
+ console.warn(
3094
+ `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3095
+ );
3096
+ opts?.onLateSkip?.(note, delay);
3097
+ }
3057
3098
  }
3058
- }
3059
- })();
3060
- } else {
3061
- const when = t0 - ctx.currentTime;
3062
- const dest = options.getTrackDestination?.(track.id ?? "");
3063
- const effPeak = dest ? peak * masterVolumeScalar : peak;
3064
- model(note.syllable, {
3065
- trackId: track.id ?? "",
3066
- pitch: note.pitch,
3067
- velocity: 100,
3068
- volume: effPeak,
3069
- when,
3070
- duration: note.durationSec,
3071
- pan: track.pan,
3072
- reverbSend: track.reverbSend,
3073
- delaySend: track.delaySend,
3074
- destination: dest
3075
- });
3076
- opts?.onScheduled?.(track, note, t0);
3099
+ })();
3100
+ } else {
3101
+ const when = t0 - ctx.currentTime;
3102
+ const dest = options.getTrackDestination?.(track.id ?? "");
3103
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3104
+ model(note.syllable, {
3105
+ trackId: track.id ?? "",
3106
+ pitch,
3107
+ velocity: 100,
3108
+ volume: effPeak,
3109
+ when,
3110
+ duration: note.durationSec,
3111
+ pan: track.pan,
3112
+ reverbSend: track.reverbSend,
3113
+ delaySend: track.delaySend,
3114
+ destination: dest
3115
+ });
3116
+ opts?.onScheduled?.(track, note, t0);
3117
+ }
3118
+ };
3119
+ dispatchNote(note.pitch, 1);
3120
+ for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
3121
+ dispatchNote(note.pitch + offset, OCTAVE_UNISON_PEAK_SCALE);
3122
+ }
3123
+ if (!(model.renderToCache && model.scheduleCached)) {
3077
3124
  await new Promise((resolve) => setTimeout(resolve, 0));
3078
3125
  }
3079
3126
  }
@@ -3132,10 +3179,11 @@ var PITCH_MAP = {
3132
3179
  b: 11
3133
3180
  };
3134
3181
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3135
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mode)=([\w-]+)/gi;
3182
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3136
3183
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3137
3184
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3138
3185
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3186
+ var TRACK_REVERBSEND_DIRECTIVE = /#t(\d+)rev=(\d+)/gi;
3139
3187
  var TRACK_EQLOW_DIRECTIVE = /#t(\d+)eqlo=(-?\d+)/gi;
3140
3188
  var TRACK_EQMID_DIRECTIVE = /#t(\d+)eqmid=(-?\d+)/gi;
3141
3189
  var TRACK_EQHIGH_DIRECTIVE = /#t(\d+)eqhi=(-?\d+)/gi;
@@ -3166,6 +3214,12 @@ var parseMmlMeta = (mml) => {
3166
3214
  if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3167
3215
  } else if (key === "delaydiv") {
3168
3216
  if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3217
+ } else if (key === "fadein") {
3218
+ const fi = Number.parseInt(m[2], 10);
3219
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3220
+ } else if (key === "fadeout") {
3221
+ const fo = Number.parseInt(m[2], 10);
3222
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3169
3223
  } else if (key === "mode") {
3170
3224
  if (m[2] === "simple" || m[2] === "advanced") {
3171
3225
  meta.mode = m[2];
@@ -3196,6 +3250,14 @@ var parseMmlMeta = (mml) => {
3196
3250
  meta.trackWidth[idx] = val;
3197
3251
  }
3198
3252
  }
3253
+ for (const m of mml.matchAll(TRACK_REVERBSEND_DIRECTIVE)) {
3254
+ const idx = Number.parseInt(m[1], 10);
3255
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3256
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3257
+ meta.trackReverbSend ??= {};
3258
+ meta.trackReverbSend[idx] = val;
3259
+ }
3260
+ }
3199
3261
  for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3200
3262
  const idx = Number.parseInt(m[1], 10);
3201
3263
  const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
@@ -3222,7 +3284,7 @@ var parseMmlMeta = (mml) => {
3222
3284
  }
3223
3285
  return meta;
3224
3286
  };
3225
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "").replace(TRACK_EQLOW_DIRECTIVE, "").replace(TRACK_EQMID_DIRECTIVE, "").replace(TRACK_EQHIGH_DIRECTIVE, "");
3287
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "").replace(TRACK_REVERBSEND_DIRECTIVE, "").replace(TRACK_EQLOW_DIRECTIVE, "").replace(TRACK_EQMID_DIRECTIVE, "").replace(TRACK_EQHIGH_DIRECTIVE, "");
3226
3288
  var formatMmlMeta = (meta, space = "") => {
3227
3289
  const parts = [];
3228
3290
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3241,6 +3303,10 @@ var formatMmlMeta = (meta, space = "") => {
3241
3303
  parts.push(`#delay=${meta.delay}`);
3242
3304
  if (meta.delayDivision && meta.delayDivision !== "8")
3243
3305
  parts.push(`#delaydiv=${meta.delayDivision}`);
3306
+ if (meta.fadeIn !== void 0 && meta.fadeIn !== 0)
3307
+ parts.push(`#fadein=${meta.fadeIn}`);
3308
+ if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
3309
+ parts.push(`#fadeout=${meta.fadeOut}`);
3244
3310
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3245
3311
  if (meta.trackInstruments) {
3246
3312
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
@@ -3257,6 +3323,11 @@ var formatMmlMeta = (meta, space = "") => {
3257
3323
  if (val !== 100) parts.push(`#t${idx}width=${val}`);
3258
3324
  }
3259
3325
  }
3326
+ if (meta.trackReverbSend) {
3327
+ for (const [idx, val] of Object.entries(meta.trackReverbSend)) {
3328
+ if (val !== 0) parts.push(`#t${idx}rev=${val}`);
3329
+ }
3330
+ }
3260
3331
  if (meta.trackEqLow) {
3261
3332
  for (const [idx, val] of Object.entries(meta.trackEqLow)) {
3262
3333
  if (val !== 0) parts.push(`#t${idx}eqlo=${val}`);
@@ -7572,11 +7643,14 @@ var DAW_CSS = `
7572
7643
  user-select: none;
7573
7644
  }
7574
7645
  .dtm-player-balloon {
7575
- position: absolute;
7576
- bottom: calc(100% + 6px);
7577
- left: 50%;
7578
- transform: translateX(-50%);
7579
- z-index: 100;
7646
+ /* body \u76F4\u4E0B\u306B\u751F\u6210\u3055\u308C\u3001JS(positionBalloon)\u304C\u6BCE\u56DE left/top \u3092\u30A2\u30F3\u30AB\u30FC\u306E\u753B\u9762\u5EA7\u6A19\u3067
7647
+ \u8A2D\u5B9A\u3059\u308B\u3002overflow:hidden \u306A\u7956\u5148\uFF08\u6295\u7A3F\u30AB\u30FC\u30C9\u7B49\uFF09\u306B\u30AF\u30EA\u30C3\u30D7\u3055\u308C\u306A\u3044\u3088\u3046 fixed \u3067\u6D6E\u304B\u305B\u308B\u3002
7648
+ top \u306F\u30A2\u30F3\u30AB\u30FC\u4E0A\u7AEF-6px\u3001transform \u3067\u81EA\u8EAB\u306E\u5DE6\u4E0B\u89D2\u3092\u305D\u306E\u70B9\u306B\u5408\u308F\u305B\u308B\u3002 */
7649
+ position: fixed;
7650
+ left: 0;
7651
+ top: 0;
7652
+ transform: translate(-50%, -100%);
7653
+ z-index: 1000;
7580
7654
  display: none;
7581
7655
  pointer-events: none;
7582
7656
  font-family: var(--dtm-font);
@@ -7605,8 +7679,8 @@ var DAW_CSS = `
7605
7679
  animation: dtm-balloon-fade-in 0.1s steps(2);
7606
7680
  }
7607
7681
  @keyframes dtm-balloon-fade-in {
7608
- from { opacity: 0; transform: translateX(-50%) translateY(4px); }
7609
- to { opacity: 1; transform: translateX(-50%) translateY(0); }
7682
+ from { opacity: 0; transform: translate(-50%, -100%) translateY(4px); }
7683
+ to { opacity: 1; transform: translate(-50%, -100%) translateY(0); }
7610
7684
  }
7611
7685
  @keyframes dtm-emoji-jump {
7612
7686
  0% { transform: translateY(0); }
@@ -8029,6 +8103,27 @@ var LYRIC_MODEL_LABELS = {
8029
8103
  };
8030
8104
  var activeBalloonEl = null;
8031
8105
  var activeBalloonTimer = null;
8106
+ var balloonAnchors = /* @__PURE__ */ new WeakMap();
8107
+ var positionBalloon = (balloonEl) => {
8108
+ const anchor = balloonAnchors.get(balloonEl);
8109
+ if (!anchor) return;
8110
+ const rect = anchor.getBoundingClientRect();
8111
+ balloonEl.style.left = `${rect.left + rect.width / 2}px`;
8112
+ balloonEl.style.top = `${rect.top - 6}px`;
8113
+ };
8114
+ var balloonListenersBound = false;
8115
+ var ensureBalloonListeners = () => {
8116
+ if (balloonListenersBound || typeof window === "undefined") return;
8117
+ balloonListenersBound = true;
8118
+ const reposition = () => {
8119
+ if (activeBalloonEl) positionBalloon(activeBalloonEl);
8120
+ };
8121
+ window.addEventListener("scroll", reposition, {
8122
+ capture: true,
8123
+ passive: true
8124
+ });
8125
+ window.addEventListener("resize", reposition, { passive: true });
8126
+ };
8032
8127
  var hideActiveBalloon = () => {
8033
8128
  if (activeBalloonEl) {
8034
8129
  activeBalloonEl.classList.remove("dtm-player-balloon--visible");
@@ -8040,6 +8135,7 @@ var hideActiveBalloon = () => {
8040
8135
  }
8041
8136
  };
8042
8137
  var showBalloon = (balloonEl) => {
8138
+ positionBalloon(balloonEl);
8043
8139
  if (activeBalloonEl === balloonEl) {
8044
8140
  if (activeBalloonTimer) {
8045
8141
  clearTimeout(activeBalloonTimer);
@@ -8304,6 +8400,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8304
8400
  const doc = target.ownerDocument ?? document;
8305
8401
  const root = doc.createElement("div");
8306
8402
  root.className = "dtm-daw dtm-player";
8403
+ const playerBalloons = [];
8307
8404
  const head = doc.createElement("div");
8308
8405
  head.className = "dtm-player-head";
8309
8406
  const playBtn = doc.createElement("button");
@@ -8649,7 +8746,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8649
8746
  balloon.className = "dtm-player-balloon";
8650
8747
  const modelKey = lt.model.toLowerCase();
8651
8748
  balloon.textContent = LYRIC_MODEL_LABELS[modelKey] ?? lt.model;
8652
- em.appendChild(balloon);
8749
+ balloonAnchors.set(balloon, em);
8750
+ doc.body.appendChild(balloon);
8751
+ playerBalloons.push(balloon);
8752
+ ensureBalloonListeners();
8653
8753
  em.addEventListener("mouseenter", () => {
8654
8754
  showBalloon(balloon);
8655
8755
  });
@@ -9110,6 +9210,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9110
9210
  delaySend: (lt.delay ?? 0) / 100,
9111
9211
  gender: (lt.gender ?? 50) / 100,
9112
9212
  breathiness: (lt.breathiness ?? 50) / 100,
9213
+ octaveUnison: lt.octaveUnison,
9113
9214
  notes
9114
9215
  };
9115
9216
  });
@@ -9245,9 +9346,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9245
9346
  }
9246
9347
  for (const t of blinkTimers) clearTimeout(t);
9247
9348
  clearJumpTimers();
9248
- if (activeBalloonEl && root.contains(activeBalloonEl)) {
9349
+ if (activeBalloonEl && playerBalloons.includes(activeBalloonEl)) {
9249
9350
  hideActiveBalloon();
9250
9351
  }
9352
+ for (const balloon of playerBalloons) balloon.remove();
9251
9353
  root.remove();
9252
9354
  consentOverlayEl?.remove();
9253
9355
  closeInfoModal();
@@ -10987,6 +11089,17 @@ var buildUI = (target, options) => {
10987
11089
  </select>
10988
11090
  <button class="dtm-infobtn" data-dtm="delay-amount-info" title="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10989
11091
  </div>
11092
+ <div class="dtm-row">
11093
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3</span>
11094
+ <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">
11095
+ <span class="dtm-label" data-dtm="fade-in-label">0.0s</span>
11096
+ <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>
11097
+ </div>
11098
+ <div class="dtm-row">
11099
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8</span>
11100
+ <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">
11101
+ <span class="dtm-label" data-dtm="fade-out-label">0.0s</span>
11102
+ </div>
10990
11103
  </div>
10991
11104
  </details>
10992
11105
 
@@ -11071,6 +11184,25 @@ var buildUI = (target, options) => {
11071
11184
  </select>
11072
11185
  <button class="dtm-btn dtm-btn--primary" data-dtm="shift-apply">\u9069\u7528</button>
11073
11186
  </div>
11187
+ <div class="dtm-row">
11188
+ <span class="dtm-label">\u79FB\u8ABF</span>
11189
+ <select class="dtm-select" data-dtm="transpose-select" aria-label="\u79FB\u8ABF\u3059\u308B\u534A\u97F3\u6570">
11190
+ <option value="-12">-1oct</option>
11191
+ <option value="-7">-5th</option>
11192
+ <option value="-5">-4th</option>
11193
+ <option value="-3">-3\u534A\u97F3</option>
11194
+ <option value="-2">-2\u534A\u97F3</option>
11195
+ <option value="-1">-1\u534A\u97F3</option>
11196
+ <option value="1">+1\u534A\u97F3</option>
11197
+ <option value="2">+2\u534A\u97F3</option>
11198
+ <option value="3">+3\u534A\u97F3</option>
11199
+ <option value="5">+4th</option>
11200
+ <option value="7">+5th</option>
11201
+ <option value="12">+1oct</option>
11202
+ </select>
11203
+ <button class="dtm-btn dtm-btn--primary" data-dtm="transpose-apply">\u9069\u7528</button>
11204
+ <button class="dtm-infobtn" data-dtm="transpose-info" title="\u79FB\u8ABF\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11205
+ </div>
11074
11206
  <div class="dtm-row">
11075
11207
  <button class="dtm-btn dtm-btn--danger" data-dtm="macro-clear">\u5168\u6D88\u53BB</button>
11076
11208
  <button class="dtm-btn dtm-btn--accent" data-dtm="macro-random">\u30E9\u30F3\u30C0\u30E0\u914D\u7F6E</button>
@@ -11193,6 +11325,11 @@ var buildUI = (target, options) => {
11193
11325
  delayAmountLabel: sel("delay-amount-label"),
11194
11326
  delayAmountInfoBtn: sel("delay-amount-info"),
11195
11327
  delayDivision: sel("delay-division"),
11328
+ fadeIn: sel("fade-in"),
11329
+ fadeInLabel: sel("fade-in-label"),
11330
+ fadeOut: sel("fade-out"),
11331
+ fadeOutLabel: sel("fade-out-label"),
11332
+ fadeInfoBtn: sel("fade-info"),
11196
11333
  autoMasterBtn: sel("auto-master"),
11197
11334
  autoMasterInfoBtn: sel("auto-master-info"),
11198
11335
  trackTabs: sel("track-tabs"),
@@ -11213,6 +11350,9 @@ var buildUI = (target, options) => {
11213
11350
  applyActiveOnly: sel("apply-active-only"),
11214
11351
  shiftSelect: sel("shift-select"),
11215
11352
  shiftApplyBtn: sel("shift-apply"),
11353
+ transposeSelect: sel("transpose-select"),
11354
+ transposeApplyBtn: sel("transpose-apply"),
11355
+ transposeInfoBtn: sel("transpose-info"),
11216
11356
  macroClear: sel("macro-clear"),
11217
11357
  macroRandom: sel("macro-random"),
11218
11358
  macroHarmonic: sel("macro-harmonic"),
@@ -11467,6 +11607,18 @@ var shiftNotes = (cores, shiftSteps) => {
11467
11607
  }
11468
11608
  }
11469
11609
  };
11610
+ var transposeNotes = (cores, semitones) => {
11611
+ if (semitones === 0) return;
11612
+ for (const core of cores) {
11613
+ const notes = [...core.getNotes()];
11614
+ for (const note of notes) {
11615
+ const newPitch = Math.max(0, Math.min(127, note.pitch + semitones));
11616
+ if (newPitch !== note.pitch) {
11617
+ core.moveNote(note.id, note.startStep, newPitch);
11618
+ }
11619
+ }
11620
+ }
11621
+ };
11470
11622
 
11471
11623
  // src/midi-io.ts
11472
11624
  var STEPS_PER_BEAT3 = 48;
@@ -12899,6 +13051,24 @@ var VIBRATO_INFO_HTML = `
12899
13051
  <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>
12900
13052
  </div>
12901
13053
  `;
13054
+ var OCTAVE_UNISON_TOKEN = {
13055
+ none: "",
13056
+ down: "w1",
13057
+ up: "w2",
13058
+ both: "w3"
13059
+ };
13060
+ var OCTAVE_UNISON_INFO_HTML = `
13061
+ <div class="dtm-modal-body-content">
13062
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13063
+ <p>\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u5404\u97F3\u7BC0\u306B\u3001\u3082\u30461\u58F0\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0A/\u4E0B/\u4E21\u65B9\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\u30E6\u30CB\u30BE\u30F3\uFF08\u30C0\u30D6\u30EA\u30F3\u30B0\uFF09\u300D\u3068\u3044\u3046\u5B9A\u756A\u306E\u30DC\u30FC\u30AB\u30EB\u52A0\u5DE5\u3067\u3059\u3002</p>
13064
+ <h4>\u4E0B\u30FB\u4E0A\u30FB\u4E21\u65B9\u306E\u9055\u3044</h4>
13065
+ <p>\u300C\u4E0B\u300D\u306F\u58F0\u306B\u91CD\u307F\u30FB\u30D1\u30EF\u30FC\u3092\u8DB3\u3059\u5B9A\u756A\u306E\u4F7F\u3044\u65B9\uFF08\u30ED\u30C3\u30AF/EDM\u306E\u30D0\u30C3\u30AD\u30F3\u30B0\u7B49\uFF09\u3002\u300C\u4E0A\u300D\u306F\u592A\u3055\u3067\u306F\u306A\u304F\u714C\u3073\u3084\u304B\u3055\u30FB\u53EF\u6190\u3055\u3092\u8DB3\u3059\u4F7F\u3044\u65B9\uFF08\u30A2\u30CB\u30BD\u30F3/\u30CF\u30E2\u30EA\u7B49\uFF09\u3002\u300C\u4E21\u65B9\u300D\u306F\u4E0A\u4E0B\u540C\u6642\u306B\u91CD\u306D\u308B\u7279\u6B8A\u306A\u52B9\u679C\u3067\u3001\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u6027\u3092\u5F37\u304F\u51FA\u3057\u305F\u3044\u3068\u304D\u306B\u3002</p>
13066
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13067
+ <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>
13068
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13069
+ <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\u300C\u4E21\u65B9\u300D\u306F\u5408\u6210\u304C3\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>
13070
+ </div>
13071
+ `;
12902
13072
  var GENDER_INFO_HTML = `
12903
13073
  <div class="dtm-modal-body-content">
12904
13074
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -12961,6 +13131,26 @@ var MASTER_DELAY_INFO_HTML = `
12961
13131
  <p>\u30EA\u30D0\u30FC\u30D6\u3068\u540C\u3058\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u500B\u5225\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u304C0%\u306A\u3089\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u4E0A\u3052\u3066\u3082\u7121\u97F3\u3001\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002</p>
12962
13132
  </div>
12963
13133
  `;
13134
+ var FADE_INFO_HTML = `
13135
+ <div class="dtm-modal-body-content">
13136
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13137
+ <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>
13138
+ <h4>\u3044\u3064\u52B9\u304F\u304B</h4>
13139
+ <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>
13140
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13141
+ <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>
13142
+ </div>
13143
+ `;
13144
+ var TRANSPOSE_INFO_HTML = `
13145
+ <div class="dtm-modal-body-content">
13146
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13147
+ <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>
13148
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13149
+ <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>
13150
+ <h4>\u6CE8\u610F</h4>
13151
+ <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>
13152
+ </div>
13153
+ `;
12964
13154
  var TRACK_EQ_INFO_HTML = `
12965
13155
  <div class="dtm-modal-body-content">
12966
13156
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -12991,6 +13181,16 @@ var TRACK_WIDTH_INFO_HTML = `
12991
13181
  <p>\u30B9\u30DE\u30DB\u306E\u30B9\u30D4\u30FC\u30AB\u30FC1\u500B\u306A\u3069\u3001\u30E2\u30CE\u30E9\u30EB\u306B\u8FD1\u3044\u74B0\u5883\u3067\u518D\u751F\u3059\u308B\u3068\u97F3\u304C\u8584\u304F/\u4F4D\u76F8\u304C\u4E71\u308C\u3066\u805E\u3053\u3048\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059\uFF08\u5DE6\u53F3\u306E\u5DEE\u5206\u3092\u8A87\u5F35\u3057\u3066\u3044\u308B\u305F\u3081\u3001\u8DB3\u3057\u5408\u308F\u305B\u308B\u3068\u6253\u3061\u6D88\u3057\u5408\u3046\u6210\u5206\u304C\u5897\u3048\u308B\uFF09\u3002\u4E3B\u65CB\u5F8B\u3084\u30DC\u30FC\u30AB\u30EB\u306F\u4E2D\u592E\u4ED8\u8FD1\u3067\u72ED\u3081\u3001\u30D1\u30C3\u30C9\u3084\u30B7\u30F3\u30BB\u306E\u88C5\u98FE\u30D1\u30FC\u30C8\u306F\u5E83\u3052\u308B\u3001\u3068\u3044\u3046\u306E\u304C\u30DF\u30C3\u30AF\u30B9\u306E\u5B9A\u77F3\u3067\u3059\u3002</p>
12992
13182
  </div>
12993
13183
  `;
13184
+ var TRACK_REVERBSEND_INFO_HTML = `
13185
+ <div class="dtm-modal-body-content">
13186
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13187
+ <p>\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u3092\u3001\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\uFF08[reverb]\u30D1\u30CD\u30EB\u306EDecay/Pre Delay/Mix\uFF09\u3078\u3069\u308C\u3060\u3051\u9001\u308B\u304B\u3092 0-100% \u3067\u6C7A\u3081\u307E\u3059\u30020\u306A\u3089\u9001\u3089\u306A\u3044\uFF1D\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u30EA\u30D0\u30FC\u30D6\u304C\u639B\u304B\u308A\u307E\u305B\u3093\u3002</p>
13188
+ <h4>\u300C\u58F0\u3060\u3051\u30EA\u30D0\u30FC\u30D6\u300D\u3092\u4F5C\u308B\u306B\u306F</h4>
13189
+ <p>\u30EA\u30D0\u30FC\u30D6\u306F\u5168\u30C8\u30E9\u30C3\u30AF\u3078\u4E00\u5F8B\u3067\u639B\u304B\u308B\u308F\u3051\u3067\u306F\u306A\u304F\u3001\u3053\u306E\u9001\u308A\u91CF\u3067\u500B\u5225\u306B\u6C7A\u307E\u308A\u307E\u3059\u3002\u30DC\u30FC\u30AB\u30EB\u306E\u30C8\u30E9\u30C3\u30AF\u3060\u3051\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3001\u4ED6\u306E\u697D\u5668\u30C8\u30E9\u30C3\u30AF\u306F0\u306E\u307E\u307E\u306B\u3059\u308C\u3070\u3001\u58F0\u3060\u3051\u306B\u30EA\u30D0\u30FC\u30D6\u3092\u639B\u3051\u3089\u308C\u307E\u3059\u3002\u4F4E\u57DF\u306E\u697D\u5668\uFF08\u30D9\u30FC\u30B9\u30FB\u30AD\u30C3\u30AF\u7B49\uFF09\u306F\u9001\u3089\u306A\u3044\u3001\u30DC\u30FC\u30AB\u30EB\u3084\u30D1\u30C3\u30C9\u306F\u591A\u3081\u306B\u9001\u308B\u3001\u3068\u3044\u3046\u306E\u304C\u30DF\u30C3\u30AF\u30B9\u306E\u5B9A\u77F3\u3067\u3059\u3002</p>
13190
+ <h4>[reverb]\u30D1\u30CD\u30EB\u306EMix\u3068\u306E\u95A2\u4FC2</h4>
13191
+ <p>Mix\u306F\u300C\u9001\u3089\u308C\u3066\u304D\u305F\u97F3\u3092\u3069\u308C\u3060\u3051\u8FD4\u3059\u304B\u300D\u3068\u3044\u3046\u5168\u4F53\u306E\u8FD4\u308A\u91CF\u3067\u3059\u3002\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u304C0\u306A\u3089\u3001Mix\u3092\u4E0A\u3052\u3066\u3082\u4F55\u3082\u8FD4\u3063\u3066\u304D\u307E\u305B\u3093\u3002\u307E\u305A\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u6C7A\u3081\u3066\u304B\u3089\u3001Mix\u3067\u5168\u4F53\u306E\u639B\u304B\u308A\u5177\u5408\u3092\u5FAE\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
13192
+ </div>
13193
+ `;
12994
13194
  var AUTO_MASTER_INFO_HTML = `
12995
13195
  <div class="dtm-modal-body-content">
12996
13196
  <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
@@ -13296,6 +13496,10 @@ var mountDAW = (target, options = {}) => {
13296
13496
  refs.delayAmount.value = String(options.delayAmount ?? 0);
13297
13497
  refs.delayAmountLabel.textContent = `${options.delayAmount ?? 0}%`;
13298
13498
  refs.delayDivision.value = options.delayDivision ?? "8";
13499
+ refs.fadeIn.value = String(options.fadeInSec ?? 0);
13500
+ refs.fadeInLabel.textContent = `${(options.fadeInSec ?? 0).toFixed(1)}s`;
13501
+ refs.fadeOut.value = String(options.fadeOutSec ?? 0);
13502
+ refs.fadeOutLabel.textContent = `${(options.fadeOutSec ?? 0).toFixed(1)}s`;
13299
13503
  refs.drumVolume.value = String(options.drumVolume ?? 80);
13300
13504
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
13301
13505
  const renderConfig = {
@@ -13316,6 +13520,8 @@ var mountDAW = (target, options = {}) => {
13316
13520
  let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13317
13521
  let delayAmount = options.delayAmount ?? 0;
13318
13522
  let delayDivision = options.delayDivision ?? "8";
13523
+ let fadeInSec = options.fadeInSec ?? 0;
13524
+ let fadeOutSec = options.fadeOutSec ?? 0;
13319
13525
  let unsubscribeClip;
13320
13526
  options.onReverbChange?.(reverbAmount);
13321
13527
  options.onReverbDecayChange?.(reverbDecay);
@@ -13404,7 +13610,8 @@ var mountDAW = (target, options = {}) => {
13404
13610
  vocalReverb: t.vocalReverb,
13405
13611
  vocalDelay: t.vocalDelay,
13406
13612
  vocalGender: t.vocalGender,
13407
- vocalBreathiness: t.vocalBreathiness
13613
+ vocalBreathiness: t.vocalBreathiness,
13614
+ vocalOctaveUnison: t.vocalOctaveUnison
13408
13615
  };
13409
13616
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13410
13617
  lyricsDebounceTimer = setTimeout(() => {
@@ -13469,9 +13676,11 @@ var mountDAW = (target, options = {}) => {
13469
13676
  vocalDelay: 0,
13470
13677
  vocalGender: 50,
13471
13678
  vocalBreathiness: 50,
13679
+ vocalOctaveUnison: "none",
13472
13680
  trackInstrument: "",
13473
13681
  trackCompression: 0,
13474
13682
  trackWidth: 100,
13683
+ trackReverbSend: 0,
13475
13684
  trackEqLow: 0,
13476
13685
  trackEqMid: 0,
13477
13686
  trackEqHigh: 0
@@ -13498,6 +13707,7 @@ var mountDAW = (target, options = {}) => {
13498
13707
  delay: t.vocalDelay,
13499
13708
  gender: t.vocalGender,
13500
13709
  breathiness: t.vocalBreathiness,
13710
+ octaveUnison: t.vocalOctaveUnison,
13501
13711
  syllables
13502
13712
  });
13503
13713
  });
@@ -13505,16 +13715,18 @@ var mountDAW = (target, options = {}) => {
13505
13715
  };
13506
13716
  const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
13507
13717
  let showModal;
13508
- const getMaxNoteStep = () => {
13718
+ const getSongEndStepExact = () => {
13509
13719
  let maxEndStep = 0;
13510
13720
  for (const t of trackStates) {
13511
13721
  for (const n of t.core.getNotes()) {
13512
13722
  const end = n.startStep + n.durationSteps;
13513
- if (end > maxEndStep) {
13514
- maxEndStep = end;
13515
- }
13723
+ if (end > maxEndStep) maxEndStep = end;
13516
13724
  }
13517
13725
  }
13726
+ return maxEndStep;
13727
+ };
13728
+ const getMaxNoteStep = () => {
13729
+ const maxEndStep = getSongEndStepExact();
13518
13730
  if (maxEndStep === 0) {
13519
13731
  return renderConfig.stepsPerBar * 4;
13520
13732
  }
@@ -14305,6 +14517,7 @@ var mountDAW = (target, options = {}) => {
14305
14517
  delaySend: (lt.delay ?? 0) / 100,
14306
14518
  gender: (lt.gender ?? 50) / 100,
14307
14519
  breathiness: (lt.breathiness ?? 50) / 100,
14520
+ octaveUnison: lt.octaveUnison,
14308
14521
  notes
14309
14522
  };
14310
14523
  }) : [];
@@ -14341,6 +14554,29 @@ var mountDAW = (target, options = {}) => {
14341
14554
  }
14342
14555
  playbackState = "playing";
14343
14556
  sequencer.start(fromStep);
14557
+ {
14558
+ const anchor = sequencer.getStartTime();
14559
+ const secondsPerStepFade = 60 / bpm / 48;
14560
+ const params = {};
14561
+ if (fadeInSec > 0 && fromStep === 0) {
14562
+ params.fadeInStartAt = anchor;
14563
+ params.fadeInEndAt = anchor + fadeInSec;
14564
+ }
14565
+ if (fadeOutSec > 0) {
14566
+ const endStep = getSongEndStepExact();
14567
+ const totalDurationSec = (endStep - fromStep) * secondsPerStepFade;
14568
+ if (totalDurationSec > 0) {
14569
+ const fadeOutEndAt = anchor + totalDurationSec;
14570
+ const earliestStart = params.fadeInEndAt ?? anchor;
14571
+ params.fadeOutEndAt = fadeOutEndAt;
14572
+ params.fadeOutStartAt = Math.max(
14573
+ fadeOutEndAt - fadeOutSec,
14574
+ earliestStart
14575
+ );
14576
+ }
14577
+ }
14578
+ options.onScheduleFade?.(params);
14579
+ }
14344
14580
  if (streaming && voices) {
14345
14581
  voices.startStream(streamTracks, sequencer.getStartTime(), {
14346
14582
  isAudible: (t) => !isSolo || t.id === activeTrackId,
@@ -14358,6 +14594,7 @@ var mountDAW = (target, options = {}) => {
14358
14594
  sequencer.stop();
14359
14595
  options.singingVoices?.stopStream();
14360
14596
  clearSoundTimers();
14597
+ options.onScheduleFade?.(null);
14361
14598
  playbackState = "paused";
14362
14599
  updateTransport();
14363
14600
  };
@@ -14371,6 +14608,7 @@ var mountDAW = (target, options = {}) => {
14371
14608
  sequencer.stop();
14372
14609
  options.singingVoices?.stopStream();
14373
14610
  clearSoundTimers();
14611
+ options.onScheduleFade?.(null);
14374
14612
  playbackState = "stopped";
14375
14613
  currentPlayStep = 0;
14376
14614
  updateTransport();
@@ -14441,6 +14679,12 @@ var mountDAW = (target, options = {}) => {
14441
14679
  <span class="dtm-label" data-dtm="track-width-label"></span>
14442
14680
  <button class="dtm-infobtn" data-dtm="track-width-info" title="\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14443
14681
  </div>
14682
+ <div class="dtm-row">
14683
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14684
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-reverb-send" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u3078\u306E\u9001\u308A\u91CF\uFF080=\u639B\u304B\u3089\u306A\u3044\uFF09">
14685
+ <span class="dtm-label" data-dtm="track-reverb-send-label"></span>
14686
+ <button class="dtm-infobtn" data-dtm="track-reverb-send-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14687
+ </div>
14444
14688
  </details>`;
14445
14689
  const volInput = refs.trackBody.querySelector(
14446
14690
  '[data-dtm="track-vol"]'
@@ -14545,6 +14789,28 @@ var mountDAW = (target, options = {}) => {
14545
14789
  trackWidthInfo.addEventListener("click", () => {
14546
14790
  showModal("\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC", TRACK_WIDTH_INFO_HTML);
14547
14791
  });
14792
+ const trackReverbSendInput = refs.trackBody.querySelector(
14793
+ '[data-dtm="track-reverb-send"]'
14794
+ );
14795
+ const trackReverbSendLabel = refs.trackBody.querySelector(
14796
+ '[data-dtm="track-reverb-send-label"]'
14797
+ );
14798
+ const trackReverbSendInfo = refs.trackBody.querySelector(
14799
+ '[data-dtm="track-reverb-send-info"]'
14800
+ );
14801
+ trackReverbSendInput.value = String(active.trackReverbSend);
14802
+ trackReverbSendLabel.textContent = `${active.trackReverbSend}%`;
14803
+ trackReverbSendInput.addEventListener("input", () => {
14804
+ active.trackReverbSend = Number.parseInt(trackReverbSendInput.value, 10);
14805
+ trackReverbSendLabel.textContent = `${active.trackReverbSend}%`;
14806
+ options.onTrackReverbSendChange?.(
14807
+ active.config.id,
14808
+ active.trackReverbSend
14809
+ );
14810
+ });
14811
+ trackReverbSendInfo.addEventListener("click", () => {
14812
+ showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", TRACK_REVERBSEND_INFO_HTML);
14813
+ });
14548
14814
  const instRow = document.createElement("div");
14549
14815
  instRow.className = "dtm-row";
14550
14816
  instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
@@ -14680,6 +14946,16 @@ var mountDAW = (target, options = {}) => {
14680
14946
  </span>
14681
14947
  <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14682
14948
  </div>
14949
+ <div class="dtm-row">
14950
+ <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3</span>
14951
+ <select class="dtm-select dtm-grow" data-dtm="lyric-octave-unison" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\uFF08\u3082\u30461\u58F0\u3092\u4E0A/\u4E0B\u306B\u91CD\u306D\u308B\uFF09">
14952
+ <option value="none">\u306A\u3057</option>
14953
+ <option value="down">\u4E0B (-1oct)</option>
14954
+ <option value="up">\u4E0A (+1oct)</option>
14955
+ <option value="both">\u4E0A\u4E0B\u4E21\u65B9</option>
14956
+ </select>
14957
+ <button class="dtm-infobtn" data-dtm="lyric-octave-unison-info" title="\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14958
+ </div>
14683
14959
  </details>
14684
14960
  <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>
14685
14961
  </div>`;
@@ -14771,6 +15047,15 @@ var mountDAW = (target, options = {}) => {
14771
15047
  lyricVibratoInfo.addEventListener("click", () => {
14772
15048
  showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14773
15049
  });
15050
+ const lyricOctaveUnison = lyricDiv.querySelector(
15051
+ '[data-dtm="lyric-octave-unison"]'
15052
+ );
15053
+ const lyricOctaveUnisonInfo = lyricDiv.querySelector(
15054
+ '[data-dtm="lyric-octave-unison-info"]'
15055
+ );
15056
+ lyricOctaveUnisonInfo.addEventListener("click", () => {
15057
+ showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\u89E3\u8AAC", OCTAVE_UNISON_INFO_HTML);
15058
+ });
14774
15059
  const lyricTerms = lyricDiv.querySelector(
14775
15060
  '[data-dtm="lyric-terms"]'
14776
15061
  );
@@ -14841,6 +15126,7 @@ var mountDAW = (target, options = {}) => {
14841
15126
  lyricBreathiness.value = String(active.vocalBreathiness);
14842
15127
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14843
15128
  lyricVibrato.checked = active.vocalVibrato;
15129
+ lyricOctaveUnison.value = active.vocalOctaveUnison;
14844
15130
  const updateLyricCount = () => {
14845
15131
  const n = normalizeLyrics(lyricInput.value).length;
14846
15132
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -14962,6 +15248,10 @@ var mountDAW = (target, options = {}) => {
14962
15248
  active.vocalVibrato = lyricVibrato.checked;
14963
15249
  fireLyricsChange(active);
14964
15250
  });
15251
+ lyricOctaveUnison.addEventListener("change", () => {
15252
+ active.vocalOctaveUnison = lyricOctaveUnison.value;
15253
+ fireLyricsChange(active);
15254
+ });
14965
15255
  lyricInput.addEventListener("input", () => {
14966
15256
  active.lyrics = lyricInput.value;
14967
15257
  updateLyricCount();
@@ -15090,6 +15380,7 @@ var mountDAW = (target, options = {}) => {
15090
15380
  const trackInstrumentsForMeta = {};
15091
15381
  const trackCompressionForMeta = {};
15092
15382
  const trackWidthForMeta = {};
15383
+ const trackReverbSendForMeta = {};
15093
15384
  const trackEqLowForMeta = {};
15094
15385
  const trackEqMidForMeta = {};
15095
15386
  const trackEqHighForMeta = {};
@@ -15098,6 +15389,8 @@ var mountDAW = (target, options = {}) => {
15098
15389
  if (t.trackCompression !== 0)
15099
15390
  trackCompressionForMeta[i2] = t.trackCompression;
15100
15391
  if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
15392
+ if (t.trackReverbSend !== 0)
15393
+ trackReverbSendForMeta[i2] = t.trackReverbSend;
15101
15394
  if (t.trackEqLow !== 0) trackEqLowForMeta[i2] = t.trackEqLow;
15102
15395
  if (t.trackEqMid !== 0) trackEqMidForMeta[i2] = t.trackEqMid;
15103
15396
  if (t.trackEqHigh !== 0) trackEqHighForMeta[i2] = t.trackEqHigh;
@@ -15105,6 +15398,7 @@ var mountDAW = (target, options = {}) => {
15105
15398
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
15106
15399
  const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
15107
15400
  const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
15401
+ const trackReverbSendMeta = Object.keys(trackReverbSendForMeta).length > 0 ? trackReverbSendForMeta : void 0;
15108
15402
  const trackEqLowMeta = Object.keys(trackEqLowForMeta).length > 0 ? trackEqLowForMeta : void 0;
15109
15403
  const trackEqMidMeta = Object.keys(trackEqMidForMeta).length > 0 ? trackEqMidForMeta : void 0;
15110
15404
  const trackEqHighMeta = Object.keys(trackEqHighForMeta).length > 0 ? trackEqHighForMeta : void 0;
@@ -15119,10 +15413,13 @@ var mountDAW = (target, options = {}) => {
15119
15413
  reverbPreDelay,
15120
15414
  delay: delayAmount,
15121
15415
  delayDivision,
15416
+ fadeIn: Math.round(fadeInSec * 10),
15417
+ fadeOut: Math.round(fadeOutSec * 10),
15122
15418
  mode,
15123
15419
  trackInstruments: trackInstMeta,
15124
15420
  trackCompression: trackCompMeta,
15125
15421
  trackWidth: trackWidthMeta,
15422
+ trackReverbSend: trackReverbSendMeta,
15126
15423
  trackEqLow: trackEqLowMeta,
15127
15424
  trackEqMid: trackEqMidMeta,
15128
15425
  trackEqHigh: trackEqHighMeta
@@ -15140,10 +15437,13 @@ var mountDAW = (target, options = {}) => {
15140
15437
  reverbPreDelay,
15141
15438
  delay: delayAmount,
15142
15439
  delayDivision,
15440
+ fadeIn: Math.round(fadeInSec * 10),
15441
+ fadeOut: Math.round(fadeOutSec * 10),
15143
15442
  mode,
15144
15443
  trackInstruments: trackInstMeta,
15145
15444
  trackCompression: trackCompMeta,
15146
15445
  trackWidth: trackWidthMeta,
15446
+ trackReverbSend: trackReverbSendMeta,
15147
15447
  trackEqLow: trackEqLowMeta,
15148
15448
  trackEqMid: trackEqMidMeta,
15149
15449
  trackEqHigh: trackEqHighMeta
@@ -15198,7 +15498,8 @@ var mountDAW = (target, options = {}) => {
15198
15498
  rev: t.vocalReverb,
15199
15499
  del: t.vocalDelay,
15200
15500
  gen: t.vocalGender,
15201
- bre: t.vocalBreathiness
15501
+ bre: t.vocalBreathiness,
15502
+ uni: t.vocalOctaveUnison
15202
15503
  })).filter(
15203
15504
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
15204
15505
  ).map((x2) => {
@@ -15211,7 +15512,8 @@ var mountDAW = (target, options = {}) => {
15211
15512
  x2.rev === 0 ? "" : `r${x2.rev}`,
15212
15513
  x2.del === 0 ? "" : `e${x2.del}`,
15213
15514
  x2.gen === 50 ? "" : `g${x2.gen}`,
15214
- x2.bre === 50 ? "" : `h${x2.bre}`
15515
+ x2.bre === 50 ? "" : `h${x2.bre}`,
15516
+ OCTAVE_UNISON_TOKEN[x2.uni]
15215
15517
  ].filter((s) => s.length > 0).join(" ");
15216
15518
  const head = params ? `${x2.model} ${params}` : x2.model;
15217
15519
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -15380,6 +15682,16 @@ var mountDAW = (target, options = {}) => {
15380
15682
  refs.delayDivision.value = delayDivision;
15381
15683
  options.onDelayDivisionChange?.(delayDivision);
15382
15684
  }
15685
+ if (meta.fadeIn !== void 0) {
15686
+ fadeInSec = meta.fadeIn / 10;
15687
+ refs.fadeIn.value = String(fadeInSec);
15688
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
15689
+ }
15690
+ if (meta.fadeOut !== void 0) {
15691
+ fadeOutSec = meta.fadeOut / 10;
15692
+ refs.fadeOut.value = String(fadeOutSec);
15693
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
15694
+ }
15383
15695
  }
15384
15696
  trackStates.forEach((t, i2) => {
15385
15697
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -15401,6 +15713,11 @@ var mountDAW = (target, options = {}) => {
15401
15713
  t.trackWidth = width;
15402
15714
  options.onTrackWidthChange?.(t.config.id, width);
15403
15715
  }
15716
+ const reverbSend = meta.trackReverbSend?.[i2] ?? 0;
15717
+ if (t.trackReverbSend !== reverbSend) {
15718
+ t.trackReverbSend = reverbSend;
15719
+ options.onTrackReverbSendChange?.(t.config.id, reverbSend);
15720
+ }
15404
15721
  const eqLow = meta.trackEqLow?.[i2] ?? 0;
15405
15722
  if (t.trackEqLow !== eqLow) {
15406
15723
  t.trackEqLow = eqLow;
@@ -15438,6 +15755,7 @@ var mountDAW = (target, options = {}) => {
15438
15755
  active.vocalDelay = 0;
15439
15756
  active.vocalGender = 50;
15440
15757
  active.vocalBreathiness = 50;
15758
+ active.vocalOctaveUnison = "none";
15441
15759
  } else {
15442
15760
  for (const t of trackStates) {
15443
15761
  t.lyrics = "";
@@ -15451,6 +15769,7 @@ var mountDAW = (target, options = {}) => {
15451
15769
  t.vocalDelay = 0;
15452
15770
  t.vocalGender = 50;
15453
15771
  t.vocalBreathiness = 50;
15772
+ t.vocalOctaveUnison = "none";
15454
15773
  }
15455
15774
  }
15456
15775
  lyrics?.forEach((lt) => {
@@ -15468,6 +15787,7 @@ var mountDAW = (target, options = {}) => {
15468
15787
  t.vocalDelay = lt.delay ?? 0;
15469
15788
  t.vocalGender = lt.gender ?? 50;
15470
15789
  t.vocalBreathiness = lt.breathiness ?? 50;
15790
+ t.vocalOctaveUnison = lt.octaveUnison ?? "none";
15471
15791
  });
15472
15792
  for (const p of placements) {
15473
15793
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -15551,6 +15871,7 @@ var mountDAW = (target, options = {}) => {
15551
15871
  active.vocalDelay = 0;
15552
15872
  active.vocalGender = 50;
15553
15873
  active.vocalBreathiness = 50;
15874
+ active.vocalOctaveUnison = "none";
15554
15875
  } else {
15555
15876
  clearAll();
15556
15877
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -15566,6 +15887,7 @@ var mountDAW = (target, options = {}) => {
15566
15887
  t.vocalDelay = 0;
15567
15888
  t.vocalGender = 50;
15568
15889
  t.vocalBreathiness = 50;
15890
+ t.vocalOctaveUnison = "none";
15569
15891
  }
15570
15892
  }
15571
15893
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -15816,6 +16138,17 @@ var mountDAW = (target, options = {}) => {
15816
16138
  refs.delayAmountInfoBtn.addEventListener("click", () => {
15817
16139
  showModal("\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC", MASTER_DELAY_INFO_HTML);
15818
16140
  });
16141
+ refs.fadeIn.addEventListener("input", () => {
16142
+ fadeInSec = Number.parseFloat(refs.fadeIn.value) || 0;
16143
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
16144
+ });
16145
+ refs.fadeOut.addEventListener("input", () => {
16146
+ fadeOutSec = Number.parseFloat(refs.fadeOut.value) || 0;
16147
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
16148
+ });
16149
+ refs.fadeInfoBtn.addEventListener("click", () => {
16150
+ showModal("\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC", FADE_INFO_HTML);
16151
+ });
15819
16152
  refs.autoMasterInfoBtn.addEventListener("click", () => {
15820
16153
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15821
16154
  });
@@ -15854,6 +16187,10 @@ var mountDAW = (target, options = {}) => {
15854
16187
  t.trackWidth = 115;
15855
16188
  options.onTrackCompressionChange?.(t.config.id, comp);
15856
16189
  options.onTrackWidthChange?.(t.config.id, 115);
16190
+ if (!t.lyricModel) {
16191
+ t.trackReverbSend = 15;
16192
+ options.onTrackReverbSendChange?.(t.config.id, 15);
16193
+ }
15857
16194
  if (t.lyricModel) {
15858
16195
  t.vocalVibrato = true;
15859
16196
  t.vocalReverb = isMainVocal ? 15 : 30;
@@ -16093,6 +16430,20 @@ var mountDAW = (target, options = {}) => {
16093
16430
  redrawAll();
16094
16431
  })
16095
16432
  );
16433
+ refs.transposeApplyBtn.addEventListener(
16434
+ "click",
16435
+ () => overlayDuring(() => {
16436
+ transposeNotes(
16437
+ trackStates.map((t) => t.core),
16438
+ Number.parseInt(refs.transposeSelect.value, 10) || 0
16439
+ );
16440
+ redrawAll();
16441
+ updateUndoRedo();
16442
+ })
16443
+ );
16444
+ refs.transposeInfoBtn.addEventListener("click", () => {
16445
+ showModal("\u79FB\u8ABF\u306E\u89E3\u8AAC", TRANSPOSE_INFO_HTML);
16446
+ });
16096
16447
  if (showMidi) wireMidi();
16097
16448
  if (showMidiSearch) wireMidiSearch();
16098
16449
  document.addEventListener("keydown", onKeyDown);
@@ -16900,6 +17251,7 @@ var mountDAW = (target, options = {}) => {
16900
17251
  t.vocalDelay = data.vocalDelay ?? 0;
16901
17252
  t.vocalGender = data.vocalGender ?? 50;
16902
17253
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17254
+ t.vocalOctaveUnison = data.vocalOctaveUnison ?? "none";
16903
17255
  },
16904
17256
  applyTrackInstrument: (trackIndex, instrumentName) => {
16905
17257
  const t = trackStates[trackIndex];
@@ -17021,6 +17373,7 @@ var playSingingMML = async (mml, options = {}) => {
17021
17373
  delaySend: (lt.delay ?? 0) / 100,
17022
17374
  gender: (lt.gender ?? 50) / 100,
17023
17375
  breathiness: (lt.breathiness ?? 50) / 100,
17376
+ octaveUnison: lt.octaveUnison,
17024
17377
  notes
17025
17378
  };
17026
17379
  });
@@ -17714,6 +18067,10 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17714
18067
  eqHigh.connect(compressor);
17715
18068
  compressor.connect(splitter);
17716
18069
  merger.connect(destination);
18070
+ const reverbSendGain = ctx.createGain();
18071
+ reverbSendGain.gain.value = clamp5(options.reverbSend ?? 0, 0, 100) / 100;
18072
+ merger.connect(reverbSendGain);
18073
+ if (options.reverbBus) reverbSendGain.connect(options.reverbBus);
17717
18074
  return {
17718
18075
  input,
17719
18076
  setEqLow: (db) => {
@@ -17739,6 +18096,13 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17739
18096
  },
17740
18097
  setCompression: applyCompression,
17741
18098
  setWidth,
18099
+ setReverbSend: (amount) => {
18100
+ reverbSendGain.gain.setTargetAtTime(
18101
+ clamp5(amount, 0, 100) / 100,
18102
+ ctx.currentTime,
18103
+ 0.02
18104
+ );
18105
+ },
17742
18106
  dispose: () => {
17743
18107
  input.disconnect();
17744
18108
  eqLow.disconnect();
@@ -17755,6 +18119,7 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17755
18119
  outL.disconnect();
17756
18120
  outR.disconnect();
17757
18121
  merger.disconnect();
18122
+ reverbSendGain.disconnect();
17758
18123
  }
17759
18124
  };
17760
18125
  };
@@ -17976,7 +18341,6 @@ var createDtmStudio = async (options = {}) => {
17976
18341
  reverbConvolver.normalize = true;
17977
18342
  const reverbWetGain = audioCtx.createGain();
17978
18343
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
17979
- masterGain.connect(reverbPreDelay);
17980
18344
  reverbPreDelay.connect(reverbConvolver);
17981
18345
  reverbConvolver.connect(reverbWetGain);
17982
18346
  const setReverbAmount = (amount) => {
@@ -18015,13 +18379,36 @@ var createDtmStudio = async (options = {}) => {
18015
18379
  safetyLimiter.attack.value = 1e-3;
18016
18380
  safetyLimiter.release.value = 0.1;
18017
18381
  finalMix.connect(safetyLimiter);
18018
- safetyLimiter.connect(options.destination ?? audioCtx.destination);
18382
+ const fadeGain = audioCtx.createGain();
18383
+ fadeGain.gain.value = 1;
18384
+ safetyLimiter.connect(fadeGain);
18385
+ fadeGain.connect(options.destination ?? audioCtx.destination);
18386
+ const scheduleFade = (params) => {
18387
+ fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
18388
+ if (!params) {
18389
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18390
+ return;
18391
+ }
18392
+ const { fadeInStartAt, fadeInEndAt, fadeOutStartAt, fadeOutEndAt } = params;
18393
+ if (fadeInStartAt !== void 0 && fadeInEndAt !== void 0) {
18394
+ fadeGain.gain.setValueAtTime(0, fadeInStartAt);
18395
+ fadeGain.gain.linearRampToValueAtTime(1, fadeInEndAt);
18396
+ } else {
18397
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18398
+ }
18399
+ if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
18400
+ fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
18401
+ fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
18402
+ }
18403
+ };
18019
18404
  const clipMeter = createClipMeter(audioCtx, finalMix);
18020
18405
  const channelStrips = /* @__PURE__ */ new Map();
18021
18406
  const getChannelStrip = (trackId) => {
18022
18407
  let strip = channelStrips.get(trackId);
18023
18408
  if (!strip) {
18024
- strip = createChannelStrip(audioCtx, masterGain);
18409
+ strip = createChannelStrip(audioCtx, masterGain, {
18410
+ reverbBus: reverbPreDelay
18411
+ });
18025
18412
  channelStrips.set(trackId, strip);
18026
18413
  }
18027
18414
  return strip;
@@ -18058,7 +18445,8 @@ var createDtmStudio = async (options = {}) => {
18058
18445
  voicebanks,
18059
18446
  worldlineScriptUrl: options.worldlineScriptUrl,
18060
18447
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
18061
- // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
18448
+ // Convolver 入力へ直接センドする(PreDelayは経由しない。チャンネルストリップの
18449
+ // reverbSend とは別経路の、音節単位でより細かく制御できるセンド)。
18062
18450
  reverbBus: reverbConvolver,
18063
18451
  // ボーカルトラック個別のディレイセンド(`e`トークン)先。
18064
18452
  delayBus: delayBus.input,
@@ -18351,8 +18739,12 @@ var createDtmStudio = async (options = {}) => {
18351
18739
  delayDivision: options.delayDivision,
18352
18740
  onDelayDivisionChange: (division) => delayBus.setDivision(division),
18353
18741
  onBpmChange: (bpm) => delayBus.setBpm(bpm),
18742
+ fadeInSec: options.fadeInSec,
18743
+ fadeOutSec: options.fadeOutSec,
18744
+ onScheduleFade: scheduleFade,
18354
18745
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
18355
18746
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18747
+ onTrackReverbSendChange: (trackId, amount) => getChannelStrip(trackId).setReverbSend(amount),
18356
18748
  onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
18357
18749
  onTrackEqMidChange: (trackId, db) => getChannelStrip(trackId).setEqMid(db),
18358
18750
  onTrackEqHighChange: (trackId, db) => getChannelStrip(trackId).setEqHigh(db),
@@ -19045,5 +19437,6 @@ export {
19045
19437
  stripCustomVocals,
19046
19438
  stripLyrics,
19047
19439
  stripMmlMeta,
19440
+ transposeNotes,
19048
19441
  vocalVolumeToGain
19049
19442
  };