@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.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);
@@ -2319,8 +2320,9 @@ var parseLyrics = (mml) => {
2319
2320
  let delay = 0;
2320
2321
  let gender = 50;
2321
2322
  let breathiness = 50;
2323
+ let octaveUnison = "none";
2322
2324
  const modelMatch = rest.match(
2323
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghe]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2325
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2324
2326
  );
2325
2327
  let model = "";
2326
2328
  const metaTokens = [];
@@ -2396,6 +2398,13 @@ var parseLyrics = (mml) => {
2396
2398
  rest = rest.substring(eMatch[0].length).trim();
2397
2399
  continue;
2398
2400
  }
2401
+ const wMatch = rest.match(/^w([0-3])/i);
2402
+ if (wMatch) {
2403
+ octaveUnison = ["none", "down", "up", "both"][Number.parseInt(wMatch[1], 10)];
2404
+ metaTokens.push(wMatch[0]);
2405
+ rest = rest.substring(wMatch[0].length).trim();
2406
+ continue;
2407
+ }
2399
2408
  break;
2400
2409
  }
2401
2410
  const lyricLines = [rest];
@@ -2415,6 +2424,7 @@ var parseLyrics = (mml) => {
2415
2424
  delay,
2416
2425
  gender,
2417
2426
  breathiness,
2427
+ octaveUnison,
2418
2428
  syllables,
2419
2429
  metaText: metaTokens.join(" "),
2420
2430
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -3011,6 +3021,19 @@ var createKoeVoice = async (ctx, destination, options) => {
3011
3021
  var PREWARM_NOTES = 3;
3012
3022
  var STREAM_LOOKAHEAD_SEC = 1.5;
3013
3023
  var STREAM_POLL_MS = 100;
3024
+ var OCTAVE_UNISON_PEAK_SCALE = 0.6;
3025
+ var octaveUnisonOffsets = (mode) => {
3026
+ switch (mode) {
3027
+ case "down":
3028
+ return [-12];
3029
+ case "up":
3030
+ return [12];
3031
+ case "both":
3032
+ return [-12, 12];
3033
+ default:
3034
+ return [];
3035
+ }
3036
+ };
3014
3037
  var FALLBACK_MODEL = "klatt";
3015
3038
  var createSingingVoices = (ctx, destination, options = {}) => {
3016
3039
  const catalog = {};
@@ -3097,7 +3120,24 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3097
3120
  forEachSungNote(track, (note, prevVowel) => {
3098
3121
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
3099
3122
  n++;
3100
- tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
3123
+ tasks.push({
3124
+ model: m,
3125
+ note,
3126
+ prevVowel,
3127
+ pitch: note.pitch,
3128
+ vibrato: track.vibrato,
3129
+ expr
3130
+ });
3131
+ for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
3132
+ tasks.push({
3133
+ model: m,
3134
+ note,
3135
+ prevVowel,
3136
+ pitch: note.pitch + offset,
3137
+ vibrato: track.vibrato,
3138
+ expr
3139
+ });
3140
+ }
3101
3141
  });
3102
3142
  }
3103
3143
  const total = tasks.length;
@@ -3111,7 +3151,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3111
3151
  await (task.model.renderToCache?.(
3112
3152
  task.note.syllable,
3113
3153
  task.prevVowel,
3114
- task.note.pitch,
3154
+ task.pitch,
3115
3155
  task.note.durationSec * 1e3,
3116
3156
  task.vibrato,
3117
3157
  task.expr
@@ -3151,59 +3191,67 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3151
3191
  }
3152
3192
  if (opts?.isAudible && !opts.isAudible(track)) continue;
3153
3193
  const t0 = anchorTime + startSec;
3154
- if (model.renderToCache && model.scheduleCached) {
3155
- const renderToCache = model.renderToCache;
3156
- const scheduleCached = model.scheduleCached;
3157
- void (async () => {
3158
- const key = await renderToCache(
3159
- note.syllable,
3160
- prevVowel,
3161
- note.pitch,
3162
- note.durationSec * 1e3,
3163
- track.vibrato,
3164
- { gender: track.gender, breathiness: track.breathiness }
3165
- );
3166
- if (session !== streamSession) return;
3167
- if (key) {
3168
- const delay = ctx.currentTime - t0;
3169
- if (delay < 0.05) {
3170
- const dest = options.getTrackDestination?.(track.id ?? "");
3171
- const effPeak = dest ? peak * masterVolumeScalar : peak;
3172
- scheduleCached(
3173
- key,
3174
- t0,
3175
- effPeak,
3176
- track.pan,
3177
- track.reverbSend,
3178
- track.delaySend,
3179
- dest
3180
- );
3181
- opts?.onScheduled?.(track, note, t0);
3182
- } else {
3183
- console.warn(
3184
- `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3185
- );
3186
- opts?.onLateSkip?.(note, delay);
3194
+ const dispatchNote = (pitch, peakScale) => {
3195
+ if (model.renderToCache && model.scheduleCached) {
3196
+ const renderToCache = model.renderToCache;
3197
+ const scheduleCached = model.scheduleCached;
3198
+ void (async () => {
3199
+ const key = await renderToCache(
3200
+ note.syllable,
3201
+ prevVowel,
3202
+ pitch,
3203
+ note.durationSec * 1e3,
3204
+ track.vibrato,
3205
+ { gender: track.gender, breathiness: track.breathiness }
3206
+ );
3207
+ if (session !== streamSession) return;
3208
+ if (key) {
3209
+ const delay = ctx.currentTime - t0;
3210
+ if (delay < 0.05) {
3211
+ const dest = options.getTrackDestination?.(track.id ?? "");
3212
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3213
+ scheduleCached(
3214
+ key,
3215
+ t0,
3216
+ effPeak,
3217
+ track.pan,
3218
+ track.reverbSend,
3219
+ track.delaySend,
3220
+ dest
3221
+ );
3222
+ opts?.onScheduled?.(track, note, t0);
3223
+ } else {
3224
+ console.warn(
3225
+ `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3226
+ );
3227
+ opts?.onLateSkip?.(note, delay);
3228
+ }
3187
3229
  }
3188
- }
3189
- })();
3190
- } else {
3191
- const when = t0 - ctx.currentTime;
3192
- const dest = options.getTrackDestination?.(track.id ?? "");
3193
- const effPeak = dest ? peak * masterVolumeScalar : peak;
3194
- model(note.syllable, {
3195
- trackId: track.id ?? "",
3196
- pitch: note.pitch,
3197
- velocity: 100,
3198
- volume: effPeak,
3199
- when,
3200
- duration: note.durationSec,
3201
- pan: track.pan,
3202
- reverbSend: track.reverbSend,
3203
- delaySend: track.delaySend,
3204
- destination: dest
3205
- });
3206
- opts?.onScheduled?.(track, note, t0);
3230
+ })();
3231
+ } else {
3232
+ const when = t0 - ctx.currentTime;
3233
+ const dest = options.getTrackDestination?.(track.id ?? "");
3234
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3235
+ model(note.syllable, {
3236
+ trackId: track.id ?? "",
3237
+ pitch,
3238
+ velocity: 100,
3239
+ volume: effPeak,
3240
+ when,
3241
+ duration: note.durationSec,
3242
+ pan: track.pan,
3243
+ reverbSend: track.reverbSend,
3244
+ delaySend: track.delaySend,
3245
+ destination: dest
3246
+ });
3247
+ opts?.onScheduled?.(track, note, t0);
3248
+ }
3249
+ };
3250
+ dispatchNote(note.pitch, 1);
3251
+ for (const offset of octaveUnisonOffsets(track.octaveUnison)) {
3252
+ dispatchNote(note.pitch + offset, OCTAVE_UNISON_PEAK_SCALE);
3253
+ }
3254
+ if (!(model.renderToCache && model.scheduleCached)) {
3207
3255
  await new Promise((resolve) => setTimeout(resolve, 0));
3208
3256
  }
3209
3257
  }
@@ -3262,10 +3310,11 @@ var PITCH_MAP = {
3262
3310
  b: 11
3263
3311
  };
3264
3312
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3265
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mode)=([\w-]+)/gi;
3313
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3266
3314
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3267
3315
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3268
3316
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3317
+ var TRACK_REVERBSEND_DIRECTIVE = /#t(\d+)rev=(\d+)/gi;
3269
3318
  var TRACK_EQLOW_DIRECTIVE = /#t(\d+)eqlo=(-?\d+)/gi;
3270
3319
  var TRACK_EQMID_DIRECTIVE = /#t(\d+)eqmid=(-?\d+)/gi;
3271
3320
  var TRACK_EQHIGH_DIRECTIVE = /#t(\d+)eqhi=(-?\d+)/gi;
@@ -3296,6 +3345,12 @@ var parseMmlMeta = (mml) => {
3296
3345
  if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3297
3346
  } else if (key === "delaydiv") {
3298
3347
  if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3348
+ } else if (key === "fadein") {
3349
+ const fi = Number.parseInt(m[2], 10);
3350
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3351
+ } else if (key === "fadeout") {
3352
+ const fo = Number.parseInt(m[2], 10);
3353
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3299
3354
  } else if (key === "mode") {
3300
3355
  if (m[2] === "simple" || m[2] === "advanced") {
3301
3356
  meta.mode = m[2];
@@ -3326,6 +3381,14 @@ var parseMmlMeta = (mml) => {
3326
3381
  meta.trackWidth[idx] = val;
3327
3382
  }
3328
3383
  }
3384
+ for (const m of mml.matchAll(TRACK_REVERBSEND_DIRECTIVE)) {
3385
+ const idx = Number.parseInt(m[1], 10);
3386
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3387
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3388
+ meta.trackReverbSend ??= {};
3389
+ meta.trackReverbSend[idx] = val;
3390
+ }
3391
+ }
3329
3392
  for (const m of mml.matchAll(TRACK_EQLOW_DIRECTIVE)) {
3330
3393
  const idx = Number.parseInt(m[1], 10);
3331
3394
  const val = clamp2(Number.parseInt(m[2], 10), -12, 12);
@@ -3352,7 +3415,7 @@ var parseMmlMeta = (mml) => {
3352
3415
  }
3353
3416
  return meta;
3354
3417
  };
3355
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "").replace(TRACK_EQLOW_DIRECTIVE, "").replace(TRACK_EQMID_DIRECTIVE, "").replace(TRACK_EQHIGH_DIRECTIVE, "");
3418
+ 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, "");
3356
3419
  var formatMmlMeta = (meta, space = "") => {
3357
3420
  const parts = [];
3358
3421
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3371,6 +3434,10 @@ var formatMmlMeta = (meta, space = "") => {
3371
3434
  parts.push(`#delay=${meta.delay}`);
3372
3435
  if (meta.delayDivision && meta.delayDivision !== "8")
3373
3436
  parts.push(`#delaydiv=${meta.delayDivision}`);
3437
+ if (meta.fadeIn !== void 0 && meta.fadeIn !== 0)
3438
+ parts.push(`#fadein=${meta.fadeIn}`);
3439
+ if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
3440
+ parts.push(`#fadeout=${meta.fadeOut}`);
3374
3441
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3375
3442
  if (meta.trackInstruments) {
3376
3443
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
@@ -3387,6 +3454,11 @@ var formatMmlMeta = (meta, space = "") => {
3387
3454
  if (val !== 100) parts.push(`#t${idx}width=${val}`);
3388
3455
  }
3389
3456
  }
3457
+ if (meta.trackReverbSend) {
3458
+ for (const [idx, val] of Object.entries(meta.trackReverbSend)) {
3459
+ if (val !== 0) parts.push(`#t${idx}rev=${val}`);
3460
+ }
3461
+ }
3390
3462
  if (meta.trackEqLow) {
3391
3463
  for (const [idx, val] of Object.entries(meta.trackEqLow)) {
3392
3464
  if (val !== 0) parts.push(`#t${idx}eqlo=${val}`);
@@ -7702,11 +7774,14 @@ var DAW_CSS = `
7702
7774
  user-select: none;
7703
7775
  }
7704
7776
  .dtm-player-balloon {
7705
- position: absolute;
7706
- bottom: calc(100% + 6px);
7707
- left: 50%;
7708
- transform: translateX(-50%);
7709
- z-index: 100;
7777
+ /* 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
7778
+ \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
7779
+ 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 */
7780
+ position: fixed;
7781
+ left: 0;
7782
+ top: 0;
7783
+ transform: translate(-50%, -100%);
7784
+ z-index: 1000;
7710
7785
  display: none;
7711
7786
  pointer-events: none;
7712
7787
  font-family: var(--dtm-font);
@@ -7735,8 +7810,8 @@ var DAW_CSS = `
7735
7810
  animation: dtm-balloon-fade-in 0.1s steps(2);
7736
7811
  }
7737
7812
  @keyframes dtm-balloon-fade-in {
7738
- from { opacity: 0; transform: translateX(-50%) translateY(4px); }
7739
- to { opacity: 1; transform: translateX(-50%) translateY(0); }
7813
+ from { opacity: 0; transform: translate(-50%, -100%) translateY(4px); }
7814
+ to { opacity: 1; transform: translate(-50%, -100%) translateY(0); }
7740
7815
  }
7741
7816
  @keyframes dtm-emoji-jump {
7742
7817
  0% { transform: translateY(0); }
@@ -8159,6 +8234,27 @@ var LYRIC_MODEL_LABELS = {
8159
8234
  };
8160
8235
  var activeBalloonEl = null;
8161
8236
  var activeBalloonTimer = null;
8237
+ var balloonAnchors = /* @__PURE__ */ new WeakMap();
8238
+ var positionBalloon = (balloonEl) => {
8239
+ const anchor = balloonAnchors.get(balloonEl);
8240
+ if (!anchor) return;
8241
+ const rect = anchor.getBoundingClientRect();
8242
+ balloonEl.style.left = `${rect.left + rect.width / 2}px`;
8243
+ balloonEl.style.top = `${rect.top - 6}px`;
8244
+ };
8245
+ var balloonListenersBound = false;
8246
+ var ensureBalloonListeners = () => {
8247
+ if (balloonListenersBound || typeof window === "undefined") return;
8248
+ balloonListenersBound = true;
8249
+ const reposition = () => {
8250
+ if (activeBalloonEl) positionBalloon(activeBalloonEl);
8251
+ };
8252
+ window.addEventListener("scroll", reposition, {
8253
+ capture: true,
8254
+ passive: true
8255
+ });
8256
+ window.addEventListener("resize", reposition, { passive: true });
8257
+ };
8162
8258
  var hideActiveBalloon = () => {
8163
8259
  if (activeBalloonEl) {
8164
8260
  activeBalloonEl.classList.remove("dtm-player-balloon--visible");
@@ -8170,6 +8266,7 @@ var hideActiveBalloon = () => {
8170
8266
  }
8171
8267
  };
8172
8268
  var showBalloon = (balloonEl) => {
8269
+ positionBalloon(balloonEl);
8173
8270
  if (activeBalloonEl === balloonEl) {
8174
8271
  if (activeBalloonTimer) {
8175
8272
  clearTimeout(activeBalloonTimer);
@@ -8434,6 +8531,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8434
8531
  const doc = target.ownerDocument ?? document;
8435
8532
  const root = doc.createElement("div");
8436
8533
  root.className = "dtm-daw dtm-player";
8534
+ const playerBalloons = [];
8437
8535
  const head = doc.createElement("div");
8438
8536
  head.className = "dtm-player-head";
8439
8537
  const playBtn = doc.createElement("button");
@@ -8779,7 +8877,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8779
8877
  balloon.className = "dtm-player-balloon";
8780
8878
  const modelKey = lt.model.toLowerCase();
8781
8879
  balloon.textContent = LYRIC_MODEL_LABELS[modelKey] ?? lt.model;
8782
- em.appendChild(balloon);
8880
+ balloonAnchors.set(balloon, em);
8881
+ doc.body.appendChild(balloon);
8882
+ playerBalloons.push(balloon);
8883
+ ensureBalloonListeners();
8783
8884
  em.addEventListener("mouseenter", () => {
8784
8885
  showBalloon(balloon);
8785
8886
  });
@@ -9240,6 +9341,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9240
9341
  delaySend: (lt.delay ?? 0) / 100,
9241
9342
  gender: (lt.gender ?? 50) / 100,
9242
9343
  breathiness: (lt.breathiness ?? 50) / 100,
9344
+ octaveUnison: lt.octaveUnison,
9243
9345
  notes
9244
9346
  };
9245
9347
  });
@@ -9375,9 +9477,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9375
9477
  }
9376
9478
  for (const t of blinkTimers) clearTimeout(t);
9377
9479
  clearJumpTimers();
9378
- if (activeBalloonEl && root.contains(activeBalloonEl)) {
9480
+ if (activeBalloonEl && playerBalloons.includes(activeBalloonEl)) {
9379
9481
  hideActiveBalloon();
9380
9482
  }
9483
+ for (const balloon of playerBalloons) balloon.remove();
9381
9484
  root.remove();
9382
9485
  consentOverlayEl?.remove();
9383
9486
  closeInfoModal();
@@ -11117,6 +11220,17 @@ var buildUI = (target, options) => {
11117
11220
  </select>
11118
11221
  <button class="dtm-infobtn" data-dtm="delay-amount-info" title="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11119
11222
  </div>
11223
+ <div class="dtm-row">
11224
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3</span>
11225
+ <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">
11226
+ <span class="dtm-label" data-dtm="fade-in-label">0.0s</span>
11227
+ <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>
11228
+ </div>
11229
+ <div class="dtm-row">
11230
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8</span>
11231
+ <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">
11232
+ <span class="dtm-label" data-dtm="fade-out-label">0.0s</span>
11233
+ </div>
11120
11234
  </div>
11121
11235
  </details>
11122
11236
 
@@ -11201,6 +11315,25 @@ var buildUI = (target, options) => {
11201
11315
  </select>
11202
11316
  <button class="dtm-btn dtm-btn--primary" data-dtm="shift-apply">\u9069\u7528</button>
11203
11317
  </div>
11318
+ <div class="dtm-row">
11319
+ <span class="dtm-label">\u79FB\u8ABF</span>
11320
+ <select class="dtm-select" data-dtm="transpose-select" aria-label="\u79FB\u8ABF\u3059\u308B\u534A\u97F3\u6570">
11321
+ <option value="-12">-1oct</option>
11322
+ <option value="-7">-5th</option>
11323
+ <option value="-5">-4th</option>
11324
+ <option value="-3">-3\u534A\u97F3</option>
11325
+ <option value="-2">-2\u534A\u97F3</option>
11326
+ <option value="-1">-1\u534A\u97F3</option>
11327
+ <option value="1">+1\u534A\u97F3</option>
11328
+ <option value="2">+2\u534A\u97F3</option>
11329
+ <option value="3">+3\u534A\u97F3</option>
11330
+ <option value="5">+4th</option>
11331
+ <option value="7">+5th</option>
11332
+ <option value="12">+1oct</option>
11333
+ </select>
11334
+ <button class="dtm-btn dtm-btn--primary" data-dtm="transpose-apply">\u9069\u7528</button>
11335
+ <button class="dtm-infobtn" data-dtm="transpose-info" title="\u79FB\u8ABF\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11336
+ </div>
11204
11337
  <div class="dtm-row">
11205
11338
  <button class="dtm-btn dtm-btn--danger" data-dtm="macro-clear">\u5168\u6D88\u53BB</button>
11206
11339
  <button class="dtm-btn dtm-btn--accent" data-dtm="macro-random">\u30E9\u30F3\u30C0\u30E0\u914D\u7F6E</button>
@@ -11323,6 +11456,11 @@ var buildUI = (target, options) => {
11323
11456
  delayAmountLabel: sel("delay-amount-label"),
11324
11457
  delayAmountInfoBtn: sel("delay-amount-info"),
11325
11458
  delayDivision: sel("delay-division"),
11459
+ fadeIn: sel("fade-in"),
11460
+ fadeInLabel: sel("fade-in-label"),
11461
+ fadeOut: sel("fade-out"),
11462
+ fadeOutLabel: sel("fade-out-label"),
11463
+ fadeInfoBtn: sel("fade-info"),
11326
11464
  autoMasterBtn: sel("auto-master"),
11327
11465
  autoMasterInfoBtn: sel("auto-master-info"),
11328
11466
  trackTabs: sel("track-tabs"),
@@ -11343,6 +11481,9 @@ var buildUI = (target, options) => {
11343
11481
  applyActiveOnly: sel("apply-active-only"),
11344
11482
  shiftSelect: sel("shift-select"),
11345
11483
  shiftApplyBtn: sel("shift-apply"),
11484
+ transposeSelect: sel("transpose-select"),
11485
+ transposeApplyBtn: sel("transpose-apply"),
11486
+ transposeInfoBtn: sel("transpose-info"),
11346
11487
  macroClear: sel("macro-clear"),
11347
11488
  macroRandom: sel("macro-random"),
11348
11489
  macroHarmonic: sel("macro-harmonic"),
@@ -11597,6 +11738,18 @@ var shiftNotes = (cores, shiftSteps) => {
11597
11738
  }
11598
11739
  }
11599
11740
  };
11741
+ var transposeNotes = (cores, semitones) => {
11742
+ if (semitones === 0) return;
11743
+ for (const core of cores) {
11744
+ const notes = [...core.getNotes()];
11745
+ for (const note of notes) {
11746
+ const newPitch = Math.max(0, Math.min(127, note.pitch + semitones));
11747
+ if (newPitch !== note.pitch) {
11748
+ core.moveNote(note.id, note.startStep, newPitch);
11749
+ }
11750
+ }
11751
+ }
11752
+ };
11600
11753
 
11601
11754
  // src/midi-io.ts
11602
11755
  var STEPS_PER_BEAT3 = 48;
@@ -13029,6 +13182,24 @@ var VIBRATO_INFO_HTML = `
13029
13182
  <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>
13030
13183
  </div>
13031
13184
  `;
13185
+ var OCTAVE_UNISON_TOKEN = {
13186
+ none: "",
13187
+ down: "w1",
13188
+ up: "w2",
13189
+ both: "w3"
13190
+ };
13191
+ var OCTAVE_UNISON_INFO_HTML = `
13192
+ <div class="dtm-modal-body-content">
13193
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13194
+ <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>
13195
+ <h4>\u4E0B\u30FB\u4E0A\u30FB\u4E21\u65B9\u306E\u9055\u3044</h4>
13196
+ <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>
13197
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13198
+ <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>
13199
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13200
+ <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>
13201
+ </div>
13202
+ `;
13032
13203
  var GENDER_INFO_HTML = `
13033
13204
  <div class="dtm-modal-body-content">
13034
13205
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -13091,6 +13262,26 @@ var MASTER_DELAY_INFO_HTML = `
13091
13262
  <p>\u30EA\u30D0\u30FC\u30D6\u3068\u540C\u3058\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u500B\u5225\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u304C0%\u306A\u3089\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u4E0A\u3052\u3066\u3082\u7121\u97F3\u3001\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002</p>
13092
13263
  </div>
13093
13264
  `;
13265
+ var FADE_INFO_HTML = `
13266
+ <div class="dtm-modal-body-content">
13267
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13268
+ <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>
13269
+ <h4>\u3044\u3064\u52B9\u304F\u304B</h4>
13270
+ <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>
13271
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13272
+ <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>
13273
+ </div>
13274
+ `;
13275
+ var TRANSPOSE_INFO_HTML = `
13276
+ <div class="dtm-modal-body-content">
13277
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13278
+ <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>
13279
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13280
+ <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>
13281
+ <h4>\u6CE8\u610F</h4>
13282
+ <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>
13283
+ </div>
13284
+ `;
13094
13285
  var TRACK_EQ_INFO_HTML = `
13095
13286
  <div class="dtm-modal-body-content">
13096
13287
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -13121,6 +13312,16 @@ var TRACK_WIDTH_INFO_HTML = `
13121
13312
  <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>
13122
13313
  </div>
13123
13314
  `;
13315
+ var TRACK_REVERBSEND_INFO_HTML = `
13316
+ <div class="dtm-modal-body-content">
13317
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13318
+ <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>
13319
+ <h4>\u300C\u58F0\u3060\u3051\u30EA\u30D0\u30FC\u30D6\u300D\u3092\u4F5C\u308B\u306B\u306F</h4>
13320
+ <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>
13321
+ <h4>[reverb]\u30D1\u30CD\u30EB\u306EMix\u3068\u306E\u95A2\u4FC2</h4>
13322
+ <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>
13323
+ </div>
13324
+ `;
13124
13325
  var AUTO_MASTER_INFO_HTML = `
13125
13326
  <div class="dtm-modal-body-content">
13126
13327
  <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
@@ -13426,6 +13627,10 @@ var mountDAW = (target, options = {}) => {
13426
13627
  refs.delayAmount.value = String(options.delayAmount ?? 0);
13427
13628
  refs.delayAmountLabel.textContent = `${options.delayAmount ?? 0}%`;
13428
13629
  refs.delayDivision.value = options.delayDivision ?? "8";
13630
+ refs.fadeIn.value = String(options.fadeInSec ?? 0);
13631
+ refs.fadeInLabel.textContent = `${(options.fadeInSec ?? 0).toFixed(1)}s`;
13632
+ refs.fadeOut.value = String(options.fadeOutSec ?? 0);
13633
+ refs.fadeOutLabel.textContent = `${(options.fadeOutSec ?? 0).toFixed(1)}s`;
13429
13634
  refs.drumVolume.value = String(options.drumVolume ?? 80);
13430
13635
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
13431
13636
  const renderConfig = {
@@ -13446,6 +13651,8 @@ var mountDAW = (target, options = {}) => {
13446
13651
  let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13447
13652
  let delayAmount = options.delayAmount ?? 0;
13448
13653
  let delayDivision = options.delayDivision ?? "8";
13654
+ let fadeInSec = options.fadeInSec ?? 0;
13655
+ let fadeOutSec = options.fadeOutSec ?? 0;
13449
13656
  let unsubscribeClip;
13450
13657
  options.onReverbChange?.(reverbAmount);
13451
13658
  options.onReverbDecayChange?.(reverbDecay);
@@ -13534,7 +13741,8 @@ var mountDAW = (target, options = {}) => {
13534
13741
  vocalReverb: t.vocalReverb,
13535
13742
  vocalDelay: t.vocalDelay,
13536
13743
  vocalGender: t.vocalGender,
13537
- vocalBreathiness: t.vocalBreathiness
13744
+ vocalBreathiness: t.vocalBreathiness,
13745
+ vocalOctaveUnison: t.vocalOctaveUnison
13538
13746
  };
13539
13747
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13540
13748
  lyricsDebounceTimer = setTimeout(() => {
@@ -13599,9 +13807,11 @@ var mountDAW = (target, options = {}) => {
13599
13807
  vocalDelay: 0,
13600
13808
  vocalGender: 50,
13601
13809
  vocalBreathiness: 50,
13810
+ vocalOctaveUnison: "none",
13602
13811
  trackInstrument: "",
13603
13812
  trackCompression: 0,
13604
13813
  trackWidth: 100,
13814
+ trackReverbSend: 0,
13605
13815
  trackEqLow: 0,
13606
13816
  trackEqMid: 0,
13607
13817
  trackEqHigh: 0
@@ -13628,6 +13838,7 @@ var mountDAW = (target, options = {}) => {
13628
13838
  delay: t.vocalDelay,
13629
13839
  gender: t.vocalGender,
13630
13840
  breathiness: t.vocalBreathiness,
13841
+ octaveUnison: t.vocalOctaveUnison,
13631
13842
  syllables
13632
13843
  });
13633
13844
  });
@@ -13635,16 +13846,18 @@ var mountDAW = (target, options = {}) => {
13635
13846
  };
13636
13847
  const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
13637
13848
  let showModal;
13638
- const getMaxNoteStep = () => {
13849
+ const getSongEndStepExact = () => {
13639
13850
  let maxEndStep = 0;
13640
13851
  for (const t of trackStates) {
13641
13852
  for (const n of t.core.getNotes()) {
13642
13853
  const end = n.startStep + n.durationSteps;
13643
- if (end > maxEndStep) {
13644
- maxEndStep = end;
13645
- }
13854
+ if (end > maxEndStep) maxEndStep = end;
13646
13855
  }
13647
13856
  }
13857
+ return maxEndStep;
13858
+ };
13859
+ const getMaxNoteStep = () => {
13860
+ const maxEndStep = getSongEndStepExact();
13648
13861
  if (maxEndStep === 0) {
13649
13862
  return renderConfig.stepsPerBar * 4;
13650
13863
  }
@@ -14435,6 +14648,7 @@ var mountDAW = (target, options = {}) => {
14435
14648
  delaySend: (lt.delay ?? 0) / 100,
14436
14649
  gender: (lt.gender ?? 50) / 100,
14437
14650
  breathiness: (lt.breathiness ?? 50) / 100,
14651
+ octaveUnison: lt.octaveUnison,
14438
14652
  notes
14439
14653
  };
14440
14654
  }) : [];
@@ -14471,6 +14685,29 @@ var mountDAW = (target, options = {}) => {
14471
14685
  }
14472
14686
  playbackState = "playing";
14473
14687
  sequencer.start(fromStep);
14688
+ {
14689
+ const anchor = sequencer.getStartTime();
14690
+ const secondsPerStepFade = 60 / bpm / 48;
14691
+ const params = {};
14692
+ if (fadeInSec > 0 && fromStep === 0) {
14693
+ params.fadeInStartAt = anchor;
14694
+ params.fadeInEndAt = anchor + fadeInSec;
14695
+ }
14696
+ if (fadeOutSec > 0) {
14697
+ const endStep = getSongEndStepExact();
14698
+ const totalDurationSec = (endStep - fromStep) * secondsPerStepFade;
14699
+ if (totalDurationSec > 0) {
14700
+ const fadeOutEndAt = anchor + totalDurationSec;
14701
+ const earliestStart = params.fadeInEndAt ?? anchor;
14702
+ params.fadeOutEndAt = fadeOutEndAt;
14703
+ params.fadeOutStartAt = Math.max(
14704
+ fadeOutEndAt - fadeOutSec,
14705
+ earliestStart
14706
+ );
14707
+ }
14708
+ }
14709
+ options.onScheduleFade?.(params);
14710
+ }
14474
14711
  if (streaming && voices) {
14475
14712
  voices.startStream(streamTracks, sequencer.getStartTime(), {
14476
14713
  isAudible: (t) => !isSolo || t.id === activeTrackId,
@@ -14488,6 +14725,7 @@ var mountDAW = (target, options = {}) => {
14488
14725
  sequencer.stop();
14489
14726
  options.singingVoices?.stopStream();
14490
14727
  clearSoundTimers();
14728
+ options.onScheduleFade?.(null);
14491
14729
  playbackState = "paused";
14492
14730
  updateTransport();
14493
14731
  };
@@ -14501,6 +14739,7 @@ var mountDAW = (target, options = {}) => {
14501
14739
  sequencer.stop();
14502
14740
  options.singingVoices?.stopStream();
14503
14741
  clearSoundTimers();
14742
+ options.onScheduleFade?.(null);
14504
14743
  playbackState = "stopped";
14505
14744
  currentPlayStep = 0;
14506
14745
  updateTransport();
@@ -14571,6 +14810,12 @@ var mountDAW = (target, options = {}) => {
14571
14810
  <span class="dtm-label" data-dtm="track-width-label"></span>
14572
14811
  <button class="dtm-infobtn" data-dtm="track-width-info" title="\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14573
14812
  </div>
14813
+ <div class="dtm-row">
14814
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14815
+ <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">
14816
+ <span class="dtm-label" data-dtm="track-reverb-send-label"></span>
14817
+ <button class="dtm-infobtn" data-dtm="track-reverb-send-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14818
+ </div>
14574
14819
  </details>`;
14575
14820
  const volInput = refs.trackBody.querySelector(
14576
14821
  '[data-dtm="track-vol"]'
@@ -14675,6 +14920,28 @@ var mountDAW = (target, options = {}) => {
14675
14920
  trackWidthInfo.addEventListener("click", () => {
14676
14921
  showModal("\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC", TRACK_WIDTH_INFO_HTML);
14677
14922
  });
14923
+ const trackReverbSendInput = refs.trackBody.querySelector(
14924
+ '[data-dtm="track-reverb-send"]'
14925
+ );
14926
+ const trackReverbSendLabel = refs.trackBody.querySelector(
14927
+ '[data-dtm="track-reverb-send-label"]'
14928
+ );
14929
+ const trackReverbSendInfo = refs.trackBody.querySelector(
14930
+ '[data-dtm="track-reverb-send-info"]'
14931
+ );
14932
+ trackReverbSendInput.value = String(active.trackReverbSend);
14933
+ trackReverbSendLabel.textContent = `${active.trackReverbSend}%`;
14934
+ trackReverbSendInput.addEventListener("input", () => {
14935
+ active.trackReverbSend = Number.parseInt(trackReverbSendInput.value, 10);
14936
+ trackReverbSendLabel.textContent = `${active.trackReverbSend}%`;
14937
+ options.onTrackReverbSendChange?.(
14938
+ active.config.id,
14939
+ active.trackReverbSend
14940
+ );
14941
+ });
14942
+ trackReverbSendInfo.addEventListener("click", () => {
14943
+ showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", TRACK_REVERBSEND_INFO_HTML);
14944
+ });
14678
14945
  const instRow = document.createElement("div");
14679
14946
  instRow.className = "dtm-row";
14680
14947
  instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
@@ -14810,6 +15077,16 @@ var mountDAW = (target, options = {}) => {
14810
15077
  </span>
14811
15078
  <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14812
15079
  </div>
15080
+ <div class="dtm-row">
15081
+ <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3</span>
15082
+ <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">
15083
+ <option value="none">\u306A\u3057</option>
15084
+ <option value="down">\u4E0B (-1oct)</option>
15085
+ <option value="up">\u4E0A (+1oct)</option>
15086
+ <option value="both">\u4E0A\u4E0B\u4E21\u65B9</option>
15087
+ </select>
15088
+ <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>
15089
+ </div>
14813
15090
  </details>
14814
15091
  <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>
14815
15092
  </div>`;
@@ -14901,6 +15178,15 @@ var mountDAW = (target, options = {}) => {
14901
15178
  lyricVibratoInfo.addEventListener("click", () => {
14902
15179
  showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14903
15180
  });
15181
+ const lyricOctaveUnison = lyricDiv.querySelector(
15182
+ '[data-dtm="lyric-octave-unison"]'
15183
+ );
15184
+ const lyricOctaveUnisonInfo = lyricDiv.querySelector(
15185
+ '[data-dtm="lyric-octave-unison-info"]'
15186
+ );
15187
+ lyricOctaveUnisonInfo.addEventListener("click", () => {
15188
+ showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30E6\u30CB\u30BE\u30F3\u89E3\u8AAC", OCTAVE_UNISON_INFO_HTML);
15189
+ });
14904
15190
  const lyricTerms = lyricDiv.querySelector(
14905
15191
  '[data-dtm="lyric-terms"]'
14906
15192
  );
@@ -14971,6 +15257,7 @@ var mountDAW = (target, options = {}) => {
14971
15257
  lyricBreathiness.value = String(active.vocalBreathiness);
14972
15258
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14973
15259
  lyricVibrato.checked = active.vocalVibrato;
15260
+ lyricOctaveUnison.value = active.vocalOctaveUnison;
14974
15261
  const updateLyricCount = () => {
14975
15262
  const n = normalizeLyrics(lyricInput.value).length;
14976
15263
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -15092,6 +15379,10 @@ var mountDAW = (target, options = {}) => {
15092
15379
  active.vocalVibrato = lyricVibrato.checked;
15093
15380
  fireLyricsChange(active);
15094
15381
  });
15382
+ lyricOctaveUnison.addEventListener("change", () => {
15383
+ active.vocalOctaveUnison = lyricOctaveUnison.value;
15384
+ fireLyricsChange(active);
15385
+ });
15095
15386
  lyricInput.addEventListener("input", () => {
15096
15387
  active.lyrics = lyricInput.value;
15097
15388
  updateLyricCount();
@@ -15220,6 +15511,7 @@ var mountDAW = (target, options = {}) => {
15220
15511
  const trackInstrumentsForMeta = {};
15221
15512
  const trackCompressionForMeta = {};
15222
15513
  const trackWidthForMeta = {};
15514
+ const trackReverbSendForMeta = {};
15223
15515
  const trackEqLowForMeta = {};
15224
15516
  const trackEqMidForMeta = {};
15225
15517
  const trackEqHighForMeta = {};
@@ -15228,6 +15520,8 @@ var mountDAW = (target, options = {}) => {
15228
15520
  if (t.trackCompression !== 0)
15229
15521
  trackCompressionForMeta[i2] = t.trackCompression;
15230
15522
  if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
15523
+ if (t.trackReverbSend !== 0)
15524
+ trackReverbSendForMeta[i2] = t.trackReverbSend;
15231
15525
  if (t.trackEqLow !== 0) trackEqLowForMeta[i2] = t.trackEqLow;
15232
15526
  if (t.trackEqMid !== 0) trackEqMidForMeta[i2] = t.trackEqMid;
15233
15527
  if (t.trackEqHigh !== 0) trackEqHighForMeta[i2] = t.trackEqHigh;
@@ -15235,6 +15529,7 @@ var mountDAW = (target, options = {}) => {
15235
15529
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
15236
15530
  const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
15237
15531
  const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
15532
+ const trackReverbSendMeta = Object.keys(trackReverbSendForMeta).length > 0 ? trackReverbSendForMeta : void 0;
15238
15533
  const trackEqLowMeta = Object.keys(trackEqLowForMeta).length > 0 ? trackEqLowForMeta : void 0;
15239
15534
  const trackEqMidMeta = Object.keys(trackEqMidForMeta).length > 0 ? trackEqMidForMeta : void 0;
15240
15535
  const trackEqHighMeta = Object.keys(trackEqHighForMeta).length > 0 ? trackEqHighForMeta : void 0;
@@ -15249,10 +15544,13 @@ var mountDAW = (target, options = {}) => {
15249
15544
  reverbPreDelay,
15250
15545
  delay: delayAmount,
15251
15546
  delayDivision,
15547
+ fadeIn: Math.round(fadeInSec * 10),
15548
+ fadeOut: Math.round(fadeOutSec * 10),
15252
15549
  mode,
15253
15550
  trackInstruments: trackInstMeta,
15254
15551
  trackCompression: trackCompMeta,
15255
15552
  trackWidth: trackWidthMeta,
15553
+ trackReverbSend: trackReverbSendMeta,
15256
15554
  trackEqLow: trackEqLowMeta,
15257
15555
  trackEqMid: trackEqMidMeta,
15258
15556
  trackEqHigh: trackEqHighMeta
@@ -15270,10 +15568,13 @@ var mountDAW = (target, options = {}) => {
15270
15568
  reverbPreDelay,
15271
15569
  delay: delayAmount,
15272
15570
  delayDivision,
15571
+ fadeIn: Math.round(fadeInSec * 10),
15572
+ fadeOut: Math.round(fadeOutSec * 10),
15273
15573
  mode,
15274
15574
  trackInstruments: trackInstMeta,
15275
15575
  trackCompression: trackCompMeta,
15276
15576
  trackWidth: trackWidthMeta,
15577
+ trackReverbSend: trackReverbSendMeta,
15277
15578
  trackEqLow: trackEqLowMeta,
15278
15579
  trackEqMid: trackEqMidMeta,
15279
15580
  trackEqHigh: trackEqHighMeta
@@ -15328,7 +15629,8 @@ var mountDAW = (target, options = {}) => {
15328
15629
  rev: t.vocalReverb,
15329
15630
  del: t.vocalDelay,
15330
15631
  gen: t.vocalGender,
15331
- bre: t.vocalBreathiness
15632
+ bre: t.vocalBreathiness,
15633
+ uni: t.vocalOctaveUnison
15332
15634
  })).filter(
15333
15635
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
15334
15636
  ).map((x2) => {
@@ -15341,7 +15643,8 @@ var mountDAW = (target, options = {}) => {
15341
15643
  x2.rev === 0 ? "" : `r${x2.rev}`,
15342
15644
  x2.del === 0 ? "" : `e${x2.del}`,
15343
15645
  x2.gen === 50 ? "" : `g${x2.gen}`,
15344
- x2.bre === 50 ? "" : `h${x2.bre}`
15646
+ x2.bre === 50 ? "" : `h${x2.bre}`,
15647
+ OCTAVE_UNISON_TOKEN[x2.uni]
15345
15648
  ].filter((s) => s.length > 0).join(" ");
15346
15649
  const head = params ? `${x2.model} ${params}` : x2.model;
15347
15650
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -15510,6 +15813,16 @@ var mountDAW = (target, options = {}) => {
15510
15813
  refs.delayDivision.value = delayDivision;
15511
15814
  options.onDelayDivisionChange?.(delayDivision);
15512
15815
  }
15816
+ if (meta.fadeIn !== void 0) {
15817
+ fadeInSec = meta.fadeIn / 10;
15818
+ refs.fadeIn.value = String(fadeInSec);
15819
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
15820
+ }
15821
+ if (meta.fadeOut !== void 0) {
15822
+ fadeOutSec = meta.fadeOut / 10;
15823
+ refs.fadeOut.value = String(fadeOutSec);
15824
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
15825
+ }
15513
15826
  }
15514
15827
  trackStates.forEach((t, i2) => {
15515
15828
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -15531,6 +15844,11 @@ var mountDAW = (target, options = {}) => {
15531
15844
  t.trackWidth = width;
15532
15845
  options.onTrackWidthChange?.(t.config.id, width);
15533
15846
  }
15847
+ const reverbSend = meta.trackReverbSend?.[i2] ?? 0;
15848
+ if (t.trackReverbSend !== reverbSend) {
15849
+ t.trackReverbSend = reverbSend;
15850
+ options.onTrackReverbSendChange?.(t.config.id, reverbSend);
15851
+ }
15534
15852
  const eqLow = meta.trackEqLow?.[i2] ?? 0;
15535
15853
  if (t.trackEqLow !== eqLow) {
15536
15854
  t.trackEqLow = eqLow;
@@ -15568,6 +15886,7 @@ var mountDAW = (target, options = {}) => {
15568
15886
  active.vocalDelay = 0;
15569
15887
  active.vocalGender = 50;
15570
15888
  active.vocalBreathiness = 50;
15889
+ active.vocalOctaveUnison = "none";
15571
15890
  } else {
15572
15891
  for (const t of trackStates) {
15573
15892
  t.lyrics = "";
@@ -15581,6 +15900,7 @@ var mountDAW = (target, options = {}) => {
15581
15900
  t.vocalDelay = 0;
15582
15901
  t.vocalGender = 50;
15583
15902
  t.vocalBreathiness = 50;
15903
+ t.vocalOctaveUnison = "none";
15584
15904
  }
15585
15905
  }
15586
15906
  lyrics?.forEach((lt) => {
@@ -15598,6 +15918,7 @@ var mountDAW = (target, options = {}) => {
15598
15918
  t.vocalDelay = lt.delay ?? 0;
15599
15919
  t.vocalGender = lt.gender ?? 50;
15600
15920
  t.vocalBreathiness = lt.breathiness ?? 50;
15921
+ t.vocalOctaveUnison = lt.octaveUnison ?? "none";
15601
15922
  });
15602
15923
  for (const p of placements) {
15603
15924
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -15681,6 +16002,7 @@ var mountDAW = (target, options = {}) => {
15681
16002
  active.vocalDelay = 0;
15682
16003
  active.vocalGender = 50;
15683
16004
  active.vocalBreathiness = 50;
16005
+ active.vocalOctaveUnison = "none";
15684
16006
  } else {
15685
16007
  clearAll();
15686
16008
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -15696,6 +16018,7 @@ var mountDAW = (target, options = {}) => {
15696
16018
  t.vocalDelay = 0;
15697
16019
  t.vocalGender = 50;
15698
16020
  t.vocalBreathiness = 50;
16021
+ t.vocalOctaveUnison = "none";
15699
16022
  }
15700
16023
  }
15701
16024
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -15946,6 +16269,17 @@ var mountDAW = (target, options = {}) => {
15946
16269
  refs.delayAmountInfoBtn.addEventListener("click", () => {
15947
16270
  showModal("\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC", MASTER_DELAY_INFO_HTML);
15948
16271
  });
16272
+ refs.fadeIn.addEventListener("input", () => {
16273
+ fadeInSec = Number.parseFloat(refs.fadeIn.value) || 0;
16274
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
16275
+ });
16276
+ refs.fadeOut.addEventListener("input", () => {
16277
+ fadeOutSec = Number.parseFloat(refs.fadeOut.value) || 0;
16278
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
16279
+ });
16280
+ refs.fadeInfoBtn.addEventListener("click", () => {
16281
+ showModal("\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC", FADE_INFO_HTML);
16282
+ });
15949
16283
  refs.autoMasterInfoBtn.addEventListener("click", () => {
15950
16284
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15951
16285
  });
@@ -15984,6 +16318,10 @@ var mountDAW = (target, options = {}) => {
15984
16318
  t.trackWidth = 115;
15985
16319
  options.onTrackCompressionChange?.(t.config.id, comp);
15986
16320
  options.onTrackWidthChange?.(t.config.id, 115);
16321
+ if (!t.lyricModel) {
16322
+ t.trackReverbSend = 15;
16323
+ options.onTrackReverbSendChange?.(t.config.id, 15);
16324
+ }
15987
16325
  if (t.lyricModel) {
15988
16326
  t.vocalVibrato = true;
15989
16327
  t.vocalReverb = isMainVocal ? 15 : 30;
@@ -16223,6 +16561,20 @@ var mountDAW = (target, options = {}) => {
16223
16561
  redrawAll();
16224
16562
  })
16225
16563
  );
16564
+ refs.transposeApplyBtn.addEventListener(
16565
+ "click",
16566
+ () => overlayDuring(() => {
16567
+ transposeNotes(
16568
+ trackStates.map((t) => t.core),
16569
+ Number.parseInt(refs.transposeSelect.value, 10) || 0
16570
+ );
16571
+ redrawAll();
16572
+ updateUndoRedo();
16573
+ })
16574
+ );
16575
+ refs.transposeInfoBtn.addEventListener("click", () => {
16576
+ showModal("\u79FB\u8ABF\u306E\u89E3\u8AAC", TRANSPOSE_INFO_HTML);
16577
+ });
16226
16578
  if (showMidi) wireMidi();
16227
16579
  if (showMidiSearch) wireMidiSearch();
16228
16580
  document.addEventListener("keydown", onKeyDown);
@@ -17030,6 +17382,7 @@ var mountDAW = (target, options = {}) => {
17030
17382
  t.vocalDelay = data.vocalDelay ?? 0;
17031
17383
  t.vocalGender = data.vocalGender ?? 50;
17032
17384
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17385
+ t.vocalOctaveUnison = data.vocalOctaveUnison ?? "none";
17033
17386
  },
17034
17387
  applyTrackInstrument: (trackIndex, instrumentName) => {
17035
17388
  const t = trackStates[trackIndex];
@@ -17151,6 +17504,7 @@ var playSingingMML = async (mml, options = {}) => {
17151
17504
  delaySend: (lt.delay ?? 0) / 100,
17152
17505
  gender: (lt.gender ?? 50) / 100,
17153
17506
  breathiness: (lt.breathiness ?? 50) / 100,
17507
+ octaveUnison: lt.octaveUnison,
17154
17508
  notes
17155
17509
  };
17156
17510
  });
@@ -17844,6 +18198,10 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17844
18198
  eqHigh.connect(compressor);
17845
18199
  compressor.connect(splitter);
17846
18200
  merger.connect(destination);
18201
+ const reverbSendGain = ctx.createGain();
18202
+ reverbSendGain.gain.value = clamp5(options.reverbSend ?? 0, 0, 100) / 100;
18203
+ merger.connect(reverbSendGain);
18204
+ if (options.reverbBus) reverbSendGain.connect(options.reverbBus);
17847
18205
  return {
17848
18206
  input,
17849
18207
  setEqLow: (db) => {
@@ -17869,6 +18227,13 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17869
18227
  },
17870
18228
  setCompression: applyCompression,
17871
18229
  setWidth,
18230
+ setReverbSend: (amount) => {
18231
+ reverbSendGain.gain.setTargetAtTime(
18232
+ clamp5(amount, 0, 100) / 100,
18233
+ ctx.currentTime,
18234
+ 0.02
18235
+ );
18236
+ },
17872
18237
  dispose: () => {
17873
18238
  input.disconnect();
17874
18239
  eqLow.disconnect();
@@ -17885,6 +18250,7 @@ var createChannelStrip = (ctx, destination, options = {}) => {
17885
18250
  outL.disconnect();
17886
18251
  outR.disconnect();
17887
18252
  merger.disconnect();
18253
+ reverbSendGain.disconnect();
17888
18254
  }
17889
18255
  };
17890
18256
  };
@@ -18107,7 +18473,6 @@ var createDtmStudio = async (options = {}) => {
18107
18473
  reverbConvolver.normalize = true;
18108
18474
  const reverbWetGain = audioCtx.createGain();
18109
18475
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
18110
- masterGain.connect(reverbPreDelay);
18111
18476
  reverbPreDelay.connect(reverbConvolver);
18112
18477
  reverbConvolver.connect(reverbWetGain);
18113
18478
  const setReverbAmount = (amount) => {
@@ -18146,13 +18511,36 @@ var createDtmStudio = async (options = {}) => {
18146
18511
  safetyLimiter.attack.value = 1e-3;
18147
18512
  safetyLimiter.release.value = 0.1;
18148
18513
  finalMix.connect(safetyLimiter);
18149
- safetyLimiter.connect(options.destination ?? audioCtx.destination);
18514
+ const fadeGain = audioCtx.createGain();
18515
+ fadeGain.gain.value = 1;
18516
+ safetyLimiter.connect(fadeGain);
18517
+ fadeGain.connect(options.destination ?? audioCtx.destination);
18518
+ const scheduleFade = (params) => {
18519
+ fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
18520
+ if (!params) {
18521
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18522
+ return;
18523
+ }
18524
+ const { fadeInStartAt, fadeInEndAt, fadeOutStartAt, fadeOutEndAt } = params;
18525
+ if (fadeInStartAt !== void 0 && fadeInEndAt !== void 0) {
18526
+ fadeGain.gain.setValueAtTime(0, fadeInStartAt);
18527
+ fadeGain.gain.linearRampToValueAtTime(1, fadeInEndAt);
18528
+ } else {
18529
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18530
+ }
18531
+ if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
18532
+ fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
18533
+ fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
18534
+ }
18535
+ };
18150
18536
  const clipMeter = createClipMeter(audioCtx, finalMix);
18151
18537
  const channelStrips = /* @__PURE__ */ new Map();
18152
18538
  const getChannelStrip = (trackId) => {
18153
18539
  let strip = channelStrips.get(trackId);
18154
18540
  if (!strip) {
18155
- strip = createChannelStrip(audioCtx, masterGain);
18541
+ strip = createChannelStrip(audioCtx, masterGain, {
18542
+ reverbBus: reverbPreDelay
18543
+ });
18156
18544
  channelStrips.set(trackId, strip);
18157
18545
  }
18158
18546
  return strip;
@@ -18189,7 +18577,8 @@ var createDtmStudio = async (options = {}) => {
18189
18577
  voicebanks,
18190
18578
  worldlineScriptUrl: options.worldlineScriptUrl,
18191
18579
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
18192
- // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
18580
+ // Convolver 入力へ直接センドする(PreDelayは経由しない。チャンネルストリップの
18581
+ // reverbSend とは別経路の、音節単位でより細かく制御できるセンド)。
18193
18582
  reverbBus: reverbConvolver,
18194
18583
  // ボーカルトラック個別のディレイセンド(`e`トークン)先。
18195
18584
  delayBus: delayBus.input,
@@ -18482,8 +18871,12 @@ var createDtmStudio = async (options = {}) => {
18482
18871
  delayDivision: options.delayDivision,
18483
18872
  onDelayDivisionChange: (division) => delayBus.setDivision(division),
18484
18873
  onBpmChange: (bpm) => delayBus.setBpm(bpm),
18874
+ fadeInSec: options.fadeInSec,
18875
+ fadeOutSec: options.fadeOutSec,
18876
+ onScheduleFade: scheduleFade,
18485
18877
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
18486
18878
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18879
+ onTrackReverbSendChange: (trackId, amount) => getChannelStrip(trackId).setReverbSend(amount),
18487
18880
  onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
18488
18881
  onTrackEqMidChange: (trackId, db) => getChannelStrip(trackId).setEqMid(db),
18489
18882
  onTrackEqHighChange: (trackId, db) => getChannelStrip(trackId).setEqHigh(db),
@@ -19177,5 +19570,6 @@ var createDtmStudio = async (options = {}) => {
19177
19570
  stripCustomVocals,
19178
19571
  stripLyrics,
19179
19572
  stripMmlMeta,
19573
+ transposeNotes,
19180
19574
  vocalVolumeToGain
19181
19575
  });