@onjmin/dtm 0.1.86 → 0.1.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -124,6 +124,7 @@ __export(index_exports, {
124
124
  stripCustomVocals: () => stripCustomVocals,
125
125
  stripLyrics: () => stripLyrics,
126
126
  stripMmlMeta: () => stripMmlMeta,
127
+ transposeNotes: () => transposeNotes,
127
128
  vocalVolumeToGain: () => vocalVolumeToGain
128
129
  });
129
130
  module.exports = __toCommonJS(index_exports);
@@ -2319,8 +2320,9 @@ var parseLyrics = (mml) => {
2319
2320
  let delay = 0;
2320
2321
  let gender = 50;
2321
2322
  let breathiness = 50;
2323
+ let octaveDouble = false;
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([01])/i);
2402
+ if (wMatch) {
2403
+ octaveDouble = wMatch[1] === "1";
2404
+ metaTokens.push(wMatch[0]);
2405
+ rest = rest.substring(wMatch[0].length).trim();
2406
+ continue;
2407
+ }
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
+ octaveDouble,
2418
2428
  syllables,
2419
2429
  metaText: metaTokens.join(" "),
2420
2430
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -3011,6 +3021,8 @@ 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_DOUBLE_SEMITONES = -12;
3025
+ var OCTAVE_DOUBLE_PEAK_SCALE = 0.6;
3014
3026
  var FALLBACK_MODEL = "klatt";
3015
3027
  var createSingingVoices = (ctx, destination, options = {}) => {
3016
3028
  const catalog = {};
@@ -3097,7 +3109,24 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3097
3109
  forEachSungNote(track, (note, prevVowel) => {
3098
3110
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
3099
3111
  n++;
3100
- tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
3112
+ tasks.push({
3113
+ model: m,
3114
+ note,
3115
+ prevVowel,
3116
+ pitch: note.pitch,
3117
+ vibrato: track.vibrato,
3118
+ expr
3119
+ });
3120
+ if (track.octaveDouble) {
3121
+ tasks.push({
3122
+ model: m,
3123
+ note,
3124
+ prevVowel,
3125
+ pitch: note.pitch + OCTAVE_DOUBLE_SEMITONES,
3126
+ vibrato: track.vibrato,
3127
+ expr
3128
+ });
3129
+ }
3101
3130
  });
3102
3131
  }
3103
3132
  const total = tasks.length;
@@ -3111,7 +3140,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3111
3140
  await (task.model.renderToCache?.(
3112
3141
  task.note.syllable,
3113
3142
  task.prevVowel,
3114
- task.note.pitch,
3143
+ task.pitch,
3115
3144
  task.note.durationSec * 1e3,
3116
3145
  task.vibrato,
3117
3146
  task.expr
@@ -3151,59 +3180,70 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3151
3180
  }
3152
3181
  if (opts?.isAudible && !opts.isAudible(track)) continue;
3153
3182
  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);
3183
+ const dispatchNote = (pitch, peakScale) => {
3184
+ if (model.renderToCache && model.scheduleCached) {
3185
+ const renderToCache = model.renderToCache;
3186
+ const scheduleCached = model.scheduleCached;
3187
+ void (async () => {
3188
+ const key = await renderToCache(
3189
+ note.syllable,
3190
+ prevVowel,
3191
+ pitch,
3192
+ note.durationSec * 1e3,
3193
+ track.vibrato,
3194
+ { gender: track.gender, breathiness: track.breathiness }
3195
+ );
3196
+ if (session !== streamSession) return;
3197
+ if (key) {
3198
+ const delay = ctx.currentTime - t0;
3199
+ if (delay < 0.05) {
3200
+ const dest = options.getTrackDestination?.(track.id ?? "");
3201
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3202
+ scheduleCached(
3203
+ key,
3204
+ t0,
3205
+ effPeak,
3206
+ track.pan,
3207
+ track.reverbSend,
3208
+ track.delaySend,
3209
+ dest
3210
+ );
3211
+ opts?.onScheduled?.(track, note, t0);
3212
+ } else {
3213
+ console.warn(
3214
+ `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3215
+ );
3216
+ opts?.onLateSkip?.(note, delay);
3217
+ }
3187
3218
  }
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);
3219
+ })();
3220
+ } else {
3221
+ const when = t0 - ctx.currentTime;
3222
+ const dest = options.getTrackDestination?.(track.id ?? "");
3223
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3224
+ model(note.syllable, {
3225
+ trackId: track.id ?? "",
3226
+ pitch,
3227
+ velocity: 100,
3228
+ volume: effPeak,
3229
+ when,
3230
+ duration: note.durationSec,
3231
+ pan: track.pan,
3232
+ reverbSend: track.reverbSend,
3233
+ delaySend: track.delaySend,
3234
+ destination: dest
3235
+ });
3236
+ opts?.onScheduled?.(track, note, t0);
3237
+ }
3238
+ };
3239
+ dispatchNote(note.pitch, 1);
3240
+ if (track.octaveDouble) {
3241
+ dispatchNote(
3242
+ note.pitch + OCTAVE_DOUBLE_SEMITONES,
3243
+ OCTAVE_DOUBLE_PEAK_SCALE
3244
+ );
3245
+ }
3246
+ if (!(model.renderToCache && model.scheduleCached)) {
3207
3247
  await new Promise((resolve) => setTimeout(resolve, 0));
3208
3248
  }
3209
3249
  }
@@ -3262,7 +3302,7 @@ var PITCH_MAP = {
3262
3302
  b: 11
3263
3303
  };
3264
3304
  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;
3305
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3266
3306
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3267
3307
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3268
3308
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
@@ -3296,6 +3336,12 @@ var parseMmlMeta = (mml) => {
3296
3336
  if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3297
3337
  } else if (key === "delaydiv") {
3298
3338
  if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3339
+ } else if (key === "fadein") {
3340
+ const fi = Number.parseInt(m[2], 10);
3341
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3342
+ } else if (key === "fadeout") {
3343
+ const fo = Number.parseInt(m[2], 10);
3344
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3299
3345
  } else if (key === "mode") {
3300
3346
  if (m[2] === "simple" || m[2] === "advanced") {
3301
3347
  meta.mode = m[2];
@@ -3371,6 +3417,10 @@ var formatMmlMeta = (meta, space = "") => {
3371
3417
  parts.push(`#delay=${meta.delay}`);
3372
3418
  if (meta.delayDivision && meta.delayDivision !== "8")
3373
3419
  parts.push(`#delaydiv=${meta.delayDivision}`);
3420
+ if (meta.fadeIn !== void 0 && meta.fadeIn !== 0)
3421
+ parts.push(`#fadein=${meta.fadeIn}`);
3422
+ if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
3423
+ parts.push(`#fadeout=${meta.fadeOut}`);
3374
3424
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3375
3425
  if (meta.trackInstruments) {
3376
3426
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
@@ -9240,6 +9290,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9240
9290
  delaySend: (lt.delay ?? 0) / 100,
9241
9291
  gender: (lt.gender ?? 50) / 100,
9242
9292
  breathiness: (lt.breathiness ?? 50) / 100,
9293
+ octaveDouble: lt.octaveDouble,
9243
9294
  notes
9244
9295
  };
9245
9296
  });
@@ -11117,6 +11168,17 @@ var buildUI = (target, options) => {
11117
11168
  </select>
11118
11169
  <button class="dtm-infobtn" data-dtm="delay-amount-info" title="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11119
11170
  </div>
11171
+ <div class="dtm-row">
11172
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3</span>
11173
+ <input type="range" class="dtm-range dtm-grow" data-dtm="fade-in" value="0" min="0" max="10" step="0.5" aria-label="\u66F2\u982D\u306E\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u9577\uFF08\u79D2\uFF09">
11174
+ <span class="dtm-label" data-dtm="fade-in-label">0.0s</span>
11175
+ <button class="dtm-infobtn" data-dtm="fade-info" title="\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11176
+ </div>
11177
+ <div class="dtm-row">
11178
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8</span>
11179
+ <input type="range" class="dtm-range dtm-grow" data-dtm="fade-out" value="0" min="0" max="10" step="0.5" aria-label="\u66F2\u5C3E\u306E\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u9577\uFF08\u79D2\uFF09">
11180
+ <span class="dtm-label" data-dtm="fade-out-label">0.0s</span>
11181
+ </div>
11120
11182
  </div>
11121
11183
  </details>
11122
11184
 
@@ -11201,6 +11263,25 @@ var buildUI = (target, options) => {
11201
11263
  </select>
11202
11264
  <button class="dtm-btn dtm-btn--primary" data-dtm="shift-apply">\u9069\u7528</button>
11203
11265
  </div>
11266
+ <div class="dtm-row">
11267
+ <span class="dtm-label">\u79FB\u8ABF</span>
11268
+ <select class="dtm-select" data-dtm="transpose-select" aria-label="\u79FB\u8ABF\u3059\u308B\u534A\u97F3\u6570">
11269
+ <option value="-12">-1oct</option>
11270
+ <option value="-7">-5th</option>
11271
+ <option value="-5">-4th</option>
11272
+ <option value="-3">-3\u534A\u97F3</option>
11273
+ <option value="-2">-2\u534A\u97F3</option>
11274
+ <option value="-1">-1\u534A\u97F3</option>
11275
+ <option value="1">+1\u534A\u97F3</option>
11276
+ <option value="2">+2\u534A\u97F3</option>
11277
+ <option value="3">+3\u534A\u97F3</option>
11278
+ <option value="5">+4th</option>
11279
+ <option value="7">+5th</option>
11280
+ <option value="12">+1oct</option>
11281
+ </select>
11282
+ <button class="dtm-btn dtm-btn--primary" data-dtm="transpose-apply">\u9069\u7528</button>
11283
+ <button class="dtm-infobtn" data-dtm="transpose-info" title="\u79FB\u8ABF\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11284
+ </div>
11204
11285
  <div class="dtm-row">
11205
11286
  <button class="dtm-btn dtm-btn--danger" data-dtm="macro-clear">\u5168\u6D88\u53BB</button>
11206
11287
  <button class="dtm-btn dtm-btn--accent" data-dtm="macro-random">\u30E9\u30F3\u30C0\u30E0\u914D\u7F6E</button>
@@ -11323,6 +11404,11 @@ var buildUI = (target, options) => {
11323
11404
  delayAmountLabel: sel("delay-amount-label"),
11324
11405
  delayAmountInfoBtn: sel("delay-amount-info"),
11325
11406
  delayDivision: sel("delay-division"),
11407
+ fadeIn: sel("fade-in"),
11408
+ fadeInLabel: sel("fade-in-label"),
11409
+ fadeOut: sel("fade-out"),
11410
+ fadeOutLabel: sel("fade-out-label"),
11411
+ fadeInfoBtn: sel("fade-info"),
11326
11412
  autoMasterBtn: sel("auto-master"),
11327
11413
  autoMasterInfoBtn: sel("auto-master-info"),
11328
11414
  trackTabs: sel("track-tabs"),
@@ -11343,6 +11429,9 @@ var buildUI = (target, options) => {
11343
11429
  applyActiveOnly: sel("apply-active-only"),
11344
11430
  shiftSelect: sel("shift-select"),
11345
11431
  shiftApplyBtn: sel("shift-apply"),
11432
+ transposeSelect: sel("transpose-select"),
11433
+ transposeApplyBtn: sel("transpose-apply"),
11434
+ transposeInfoBtn: sel("transpose-info"),
11346
11435
  macroClear: sel("macro-clear"),
11347
11436
  macroRandom: sel("macro-random"),
11348
11437
  macroHarmonic: sel("macro-harmonic"),
@@ -11597,6 +11686,18 @@ var shiftNotes = (cores, shiftSteps) => {
11597
11686
  }
11598
11687
  }
11599
11688
  };
11689
+ var transposeNotes = (cores, semitones) => {
11690
+ if (semitones === 0) return;
11691
+ for (const core of cores) {
11692
+ const notes = [...core.getNotes()];
11693
+ for (const note of notes) {
11694
+ const newPitch = Math.max(0, Math.min(127, note.pitch + semitones));
11695
+ if (newPitch !== note.pitch) {
11696
+ core.moveNote(note.id, note.startStep, newPitch);
11697
+ }
11698
+ }
11699
+ }
11700
+ };
11600
11701
 
11601
11702
  // src/midi-io.ts
11602
11703
  var STEPS_PER_BEAT3 = 48;
@@ -13029,6 +13130,16 @@ var VIBRATO_INFO_HTML = `
13029
13130
  <p>\u30D4\u30C3\u30C1\u3092\u63FA\u3089\u3059\u52B9\u679C\u306A\u306E\u3067\u3001\u58F0\u8CEA\u305D\u306E\u3082\u306E\u3092\u5909\u3048\u308B\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306F\u72EC\u7ACB\u3057\u3066\u7D44\u307F\u5408\u308F\u305B\u3089\u308C\u307E\u3059\u3002\u901F\u3055\u30FB\u6DF1\u3055\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\uFF08\u6B4C\u3068\u3057\u3066\u7834\u7DBB\u3057\u306B\u304F\u3044\u63A7\u3048\u3081\u306A\u91CF\u306B\u56FA\u5B9A\uFF09\u3002\u66F2\u3084\u7B87\u6240\u3054\u3068\u306B\u639B\u3051\u305F\u3044/\u639B\u3051\u305F\u304F\u306A\u3044\u304C\u3042\u308B\u5834\u5408\u306F\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u6B4C\u8A5E\u3092\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002</p>
13030
13131
  </div>
13031
13132
  `;
13133
+ var OCTAVE_DOUBLE_INFO_HTML = `
13134
+ <div class="dtm-modal-body-content">
13135
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13136
+ <p>ON\u306B\u3059\u308B\u3068\u3001\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u5404\u97F3\u7BC0\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\uFF08\u63A7\u3048\u3081\u306A\u97F3\u91CF\uFF09\u3067\u540C\u6642\u306B\u91CD\u306D\u3066\u767A\u97F3\u3057\u307E\u3059\u3002\u58F0\u306B\u539A\u307F\u30FB\u30D1\u30EF\u30FC\u3092\u8DB3\u3059\u300C\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EA\u30F3\u30B0\u300D\u3068\u3044\u3046\u5B9A\u756A\u306E\u30DC\u30FC\u30AB\u30EB\u52A0\u5DE5\u3067\u3059\u3002</p>
13137
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13138
+ <p>\u30B5\u30D3\u306E\u6C7A\u3081\u306E\u30D5\u30EC\u30FC\u30BA\u3001\u30ED\u30DC\u30C3\u30C8\u7684/\u30C0\u30FC\u30AF\u306A\u8CEA\u611F\u306E\u6F14\u51FA\u3001\u30E6\u30CB\u30BE\u30F3\u30CF\u30FC\u30E2\u30CB\u30FC\u306A\u3069\u3067\u3088\u304F\u4F7F\u308F\u308C\u307E\u3059\u3002\u66F2\u5168\u4F53\u306B\u639B\u3051\u3063\u3071\u306A\u3057\u306B\u3059\u308B\u3068\u5E38\u306B\u304F\u3069\u3044\u5370\u8C61\u306B\u306A\u308A\u3084\u3059\u3044\u306E\u3067\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u4F7F\u3044\u305F\u3044\u7B87\u6240\u3060\u3051\u306B\u7D5E\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
13139
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13140
+ <p>\u91CD\u306D\u308B\u58F0\u306B\u3082\u540C\u3058\u30D3\u30D6\u30E9\u30FC\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9/\u30EA\u30D0\u30FC\u30D6\u9001\u308A/\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u8A2D\u5B9A\u304C\u305D\u306E\u307E\u307E\u9069\u7528\u3055\u308C\u307E\u3059\u3002\u97F3\u91CF\u6BD4\u30FB\u97F3\u7A0B\u5DEE\uFF081\u30AA\u30AF\u30BF\u30FC\u30D6\u56FA\u5B9A\uFF09\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\u3002\u5408\u6210\u304C\u5358\u7D14\u306B2\u500D\u8D70\u308B\u305F\u3081\u3001\u97F3\u6570\u306E\u591A\u3044\u30C8\u30E9\u30C3\u30AF\u3067\u306F\u3084\u3084\u91CD\u304F\u306A\u308A\u307E\u3059\u3002</p>
13141
+ </div>
13142
+ `;
13032
13143
  var GENDER_INFO_HTML = `
13033
13144
  <div class="dtm-modal-body-content">
13034
13145
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -13091,6 +13202,26 @@ var MASTER_DELAY_INFO_HTML = `
13091
13202
  <p>\u30EA\u30D0\u30FC\u30D6\u3068\u540C\u3058\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u500B\u5225\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u304C0%\u306A\u3089\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u4E0A\u3052\u3066\u3082\u7121\u97F3\u3001\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002</p>
13092
13203
  </div>
13093
13204
  `;
13205
+ var FADE_INFO_HTML = `
13206
+ <div class="dtm-modal-body-content">
13207
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13208
+ <p>\u66F2\u306E\u982D\u3067\u97F3\u91CF0%\u304B\u3089\u5F90\u3005\u306B\u4E0A\u3052\u308B\u300C\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u300D\u3001\u66F2\u306E\u7D42\u308F\u308A\u3067\u97F3\u91CF\u30920%\u307E\u3067\u5F90\u3005\u306B\u4E0B\u3052\u308B\u300C\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u300D\u3092\u8A2D\u5B9A\u3057\u307E\u3059\u30020\u79D2\u3067\u30D5\u30A7\u30FC\u30C9\u306A\u3057\u3067\u3059\u3002</p>
13209
+ <h4>\u3044\u3064\u52B9\u304F\u304B</h4>
13210
+ <p>\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u306F\u66F2\u306E\u5148\u982D\uFF080\u5C0F\u7BC0\u76EE\uFF09\u304B\u3089\u518D\u751F\u3057\u305F\u3068\u304D\u3060\u3051\u639B\u304B\u308A\u307E\u3059\u3002\u9014\u4E2D\u306E\u5C0F\u7BC0\u304B\u3089\u518D\u751F\u30FB\u30B7\u30FC\u30AF\u3057\u305F\u5834\u5408\u306F\u639B\u304B\u308A\u307E\u305B\u3093\u3002\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u306F\u3001\u4ECA\u30CE\u30FC\u30C8\u304C\u7F6E\u304B\u308C\u3066\u3044\u308B\u7BC4\u56F2\u306E\u7D42\u7AEF\u306B\u5411\u3051\u3066\u81EA\u52D5\u7684\u306B\u639B\u304B\u308A\u307E\u3059\uFF08\u30CE\u30FC\u30C8\u3092\u8DB3\u305B\u3070\u7D42\u7AEF\u3082\u4F38\u3073\u307E\u3059\uFF09\u3002</p>
13211
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13212
+ <p>\u7DE8\u96C6\u4E2D\u306F\u307B\u3068\u3093\u3069\u610F\u8B58\u3057\u307E\u305B\u3093\u304C\u3001\u66F8\u304D\u51FA\u3057\u3066\u4EBA\u306B\u8074\u304B\u305B\u308B\u30FB\u914D\u4FE1\u3059\u308B\u6BB5\u306B\u306A\u308B\u3068\u3001\u66F2\u306E\u51FA\u5165\u308A\u304C\u5510\u7A81\u3060\u3068\u7D20\u4EBA\u3063\u307D\u304F\u805E\u3053\u3048\u304C\u3061\u3067\u3059\u3002\u6570\u79D2\u306E\u30D5\u30A7\u30FC\u30C9\u3092\u5165\u308C\u308B\u3060\u3051\u3067\u4ED5\u4E0A\u304C\u308A\u306E\u5370\u8C61\u304C\u5909\u308F\u308A\u307E\u3059\u3002</p>
13213
+ </div>
13214
+ `;
13215
+ var TRANSPOSE_INFO_HTML = `
13216
+ <div class="dtm-modal-body-content">
13217
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13218
+ <p>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30CE\u30FC\u30C8\u3092\u9078\u3093\u3060\u534A\u97F3\u6570\u3060\u3051\u4E00\u62EC\u3067\u79FB\u8ABF\u3057\u307E\u3059\u3002\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306E\u6B4C\u58F0\u3082\u30CE\u30FC\u30C8\u306E\u30D4\u30C3\u30C1\u306B\u8FFD\u5F93\u3059\u308B\u305F\u3081\u3001\u30DC\u30FC\u30AB\u30EB\u3082\u4E00\u7DD2\u306B\u79FB\u8ABF\u3055\u308C\u307E\u3059\u3002</p>
13219
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13220
+ <p>\u300C\u6B4C\u3063\u3066\u307F\u305F\u3089\u97F3\u57DF\u304C\u9AD8\u3059\u304E\u305F/\u4F4E\u3059\u304E\u305F\u300D\u3068\u3044\u3046\u3068\u304D\u306E\u8ABF\u6574\u5B9A\u756A\u3067\u3059\u3002\u30DC\u30FC\u30AB\u30EB\u306E\u58F0\u57DF\u306B\u5408\u308F\u305B\u3066\u66F2\u5168\u4F53\u306E\u30AD\u30FC\u3092\u4E0A\u3052\u4E0B\u3052\u3067\u304D\u307E\u3059\u3002</p>
13221
+ <h4>\u6CE8\u610F</h4>
13222
+ <p>\u30CE\u30FC\u30C8\u30C7\u30FC\u30BF\u3092\u76F4\u63A5\u66F8\u304D\u63DB\u3048\u308B\u64CD\u4F5C\u3067\u3059\uFF08\u5143\u306B\u623B\u3059\u306B\u306F\u300C\u5143\u306B\u623B\u3059\u300D\u30DC\u30BF\u30F3\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\uFF09\u3002MIDI\u30CE\u30FC\u30C8\u7BC4\u56F2\uFF080-127\uFF09\u3092\u8D85\u3048\u308B\u97F3\u306F\u7BC4\u56F2\u5185\u306B\u30AF\u30E9\u30F3\u30D7\u3055\u308C\u307E\u3059\u3002</p>
13223
+ </div>
13224
+ `;
13094
13225
  var TRACK_EQ_INFO_HTML = `
13095
13226
  <div class="dtm-modal-body-content">
13096
13227
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -13426,6 +13557,10 @@ var mountDAW = (target, options = {}) => {
13426
13557
  refs.delayAmount.value = String(options.delayAmount ?? 0);
13427
13558
  refs.delayAmountLabel.textContent = `${options.delayAmount ?? 0}%`;
13428
13559
  refs.delayDivision.value = options.delayDivision ?? "8";
13560
+ refs.fadeIn.value = String(options.fadeInSec ?? 0);
13561
+ refs.fadeInLabel.textContent = `${(options.fadeInSec ?? 0).toFixed(1)}s`;
13562
+ refs.fadeOut.value = String(options.fadeOutSec ?? 0);
13563
+ refs.fadeOutLabel.textContent = `${(options.fadeOutSec ?? 0).toFixed(1)}s`;
13429
13564
  refs.drumVolume.value = String(options.drumVolume ?? 80);
13430
13565
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
13431
13566
  const renderConfig = {
@@ -13446,6 +13581,8 @@ var mountDAW = (target, options = {}) => {
13446
13581
  let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13447
13582
  let delayAmount = options.delayAmount ?? 0;
13448
13583
  let delayDivision = options.delayDivision ?? "8";
13584
+ let fadeInSec = options.fadeInSec ?? 0;
13585
+ let fadeOutSec = options.fadeOutSec ?? 0;
13449
13586
  let unsubscribeClip;
13450
13587
  options.onReverbChange?.(reverbAmount);
13451
13588
  options.onReverbDecayChange?.(reverbDecay);
@@ -13534,7 +13671,8 @@ var mountDAW = (target, options = {}) => {
13534
13671
  vocalReverb: t.vocalReverb,
13535
13672
  vocalDelay: t.vocalDelay,
13536
13673
  vocalGender: t.vocalGender,
13537
- vocalBreathiness: t.vocalBreathiness
13674
+ vocalBreathiness: t.vocalBreathiness,
13675
+ vocalOctaveDouble: t.vocalOctaveDouble
13538
13676
  };
13539
13677
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13540
13678
  lyricsDebounceTimer = setTimeout(() => {
@@ -13599,6 +13737,7 @@ var mountDAW = (target, options = {}) => {
13599
13737
  vocalDelay: 0,
13600
13738
  vocalGender: 50,
13601
13739
  vocalBreathiness: 50,
13740
+ vocalOctaveDouble: false,
13602
13741
  trackInstrument: "",
13603
13742
  trackCompression: 0,
13604
13743
  trackWidth: 100,
@@ -13628,6 +13767,7 @@ var mountDAW = (target, options = {}) => {
13628
13767
  delay: t.vocalDelay,
13629
13768
  gender: t.vocalGender,
13630
13769
  breathiness: t.vocalBreathiness,
13770
+ octaveDouble: t.vocalOctaveDouble,
13631
13771
  syllables
13632
13772
  });
13633
13773
  });
@@ -13635,16 +13775,18 @@ var mountDAW = (target, options = {}) => {
13635
13775
  };
13636
13776
  const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
13637
13777
  let showModal;
13638
- const getMaxNoteStep = () => {
13778
+ const getSongEndStepExact = () => {
13639
13779
  let maxEndStep = 0;
13640
13780
  for (const t of trackStates) {
13641
13781
  for (const n of t.core.getNotes()) {
13642
13782
  const end = n.startStep + n.durationSteps;
13643
- if (end > maxEndStep) {
13644
- maxEndStep = end;
13645
- }
13783
+ if (end > maxEndStep) maxEndStep = end;
13646
13784
  }
13647
13785
  }
13786
+ return maxEndStep;
13787
+ };
13788
+ const getMaxNoteStep = () => {
13789
+ const maxEndStep = getSongEndStepExact();
13648
13790
  if (maxEndStep === 0) {
13649
13791
  return renderConfig.stepsPerBar * 4;
13650
13792
  }
@@ -14435,6 +14577,7 @@ var mountDAW = (target, options = {}) => {
14435
14577
  delaySend: (lt.delay ?? 0) / 100,
14436
14578
  gender: (lt.gender ?? 50) / 100,
14437
14579
  breathiness: (lt.breathiness ?? 50) / 100,
14580
+ octaveDouble: lt.octaveDouble,
14438
14581
  notes
14439
14582
  };
14440
14583
  }) : [];
@@ -14471,6 +14614,29 @@ var mountDAW = (target, options = {}) => {
14471
14614
  }
14472
14615
  playbackState = "playing";
14473
14616
  sequencer.start(fromStep);
14617
+ {
14618
+ const anchor = sequencer.getStartTime();
14619
+ const secondsPerStepFade = 60 / bpm / 48;
14620
+ const params = {};
14621
+ if (fadeInSec > 0 && fromStep === 0) {
14622
+ params.fadeInStartAt = anchor;
14623
+ params.fadeInEndAt = anchor + fadeInSec;
14624
+ }
14625
+ if (fadeOutSec > 0) {
14626
+ const endStep = getSongEndStepExact();
14627
+ const totalDurationSec = (endStep - fromStep) * secondsPerStepFade;
14628
+ if (totalDurationSec > 0) {
14629
+ const fadeOutEndAt = anchor + totalDurationSec;
14630
+ const earliestStart = params.fadeInEndAt ?? anchor;
14631
+ params.fadeOutEndAt = fadeOutEndAt;
14632
+ params.fadeOutStartAt = Math.max(
14633
+ fadeOutEndAt - fadeOutSec,
14634
+ earliestStart
14635
+ );
14636
+ }
14637
+ }
14638
+ options.onScheduleFade?.(params);
14639
+ }
14474
14640
  if (streaming && voices) {
14475
14641
  voices.startStream(streamTracks, sequencer.getStartTime(), {
14476
14642
  isAudible: (t) => !isSolo || t.id === activeTrackId,
@@ -14488,6 +14654,7 @@ var mountDAW = (target, options = {}) => {
14488
14654
  sequencer.stop();
14489
14655
  options.singingVoices?.stopStream();
14490
14656
  clearSoundTimers();
14657
+ options.onScheduleFade?.(null);
14491
14658
  playbackState = "paused";
14492
14659
  updateTransport();
14493
14660
  };
@@ -14501,6 +14668,7 @@ var mountDAW = (target, options = {}) => {
14501
14668
  sequencer.stop();
14502
14669
  options.singingVoices?.stopStream();
14503
14670
  clearSoundTimers();
14671
+ options.onScheduleFade?.(null);
14504
14672
  playbackState = "stopped";
14505
14673
  currentPlayStep = 0;
14506
14674
  updateTransport();
@@ -14810,6 +14978,12 @@ var mountDAW = (target, options = {}) => {
14810
14978
  </span>
14811
14979
  <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14812
14980
  </div>
14981
+ <div class="dtm-row">
14982
+ <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14983
+ <input type="checkbox" data-dtm="lyric-octave-double" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB">\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB
14984
+ </span>
14985
+ <button class="dtm-infobtn" data-dtm="lyric-octave-double-info" title="\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14986
+ </div>
14813
14987
  </details>
14814
14988
  <textarea class="dtm-textarea" data-dtm="lyric-input" rows="2" placeholder="\u3072\u3089\u304C\u306A\u30FB\u30AB\u30BF\u30AB\u30CA\u3067\u6B4C\u8A5E\uFF08\u4F8B: \u3069\u308C\u307F\u3075\u3041\u305D\u3089\u3057\u3069\uFF09"></textarea>
14815
14989
  </div>`;
@@ -14901,6 +15075,15 @@ var mountDAW = (target, options = {}) => {
14901
15075
  lyricVibratoInfo.addEventListener("click", () => {
14902
15076
  showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14903
15077
  });
15078
+ const lyricOctaveDouble = lyricDiv.querySelector(
15079
+ '[data-dtm="lyric-octave-double"]'
15080
+ );
15081
+ const lyricOctaveDoubleInfo = lyricDiv.querySelector(
15082
+ '[data-dtm="lyric-octave-double-info"]'
15083
+ );
15084
+ lyricOctaveDoubleInfo.addEventListener("click", () => {
15085
+ showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u89E3\u8AAC", OCTAVE_DOUBLE_INFO_HTML);
15086
+ });
14904
15087
  const lyricTerms = lyricDiv.querySelector(
14905
15088
  '[data-dtm="lyric-terms"]'
14906
15089
  );
@@ -14971,6 +15154,7 @@ var mountDAW = (target, options = {}) => {
14971
15154
  lyricBreathiness.value = String(active.vocalBreathiness);
14972
15155
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14973
15156
  lyricVibrato.checked = active.vocalVibrato;
15157
+ lyricOctaveDouble.checked = active.vocalOctaveDouble;
14974
15158
  const updateLyricCount = () => {
14975
15159
  const n = normalizeLyrics(lyricInput.value).length;
14976
15160
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -15092,6 +15276,10 @@ var mountDAW = (target, options = {}) => {
15092
15276
  active.vocalVibrato = lyricVibrato.checked;
15093
15277
  fireLyricsChange(active);
15094
15278
  });
15279
+ lyricOctaveDouble.addEventListener("change", () => {
15280
+ active.vocalOctaveDouble = lyricOctaveDouble.checked;
15281
+ fireLyricsChange(active);
15282
+ });
15095
15283
  lyricInput.addEventListener("input", () => {
15096
15284
  active.lyrics = lyricInput.value;
15097
15285
  updateLyricCount();
@@ -15249,6 +15437,8 @@ var mountDAW = (target, options = {}) => {
15249
15437
  reverbPreDelay,
15250
15438
  delay: delayAmount,
15251
15439
  delayDivision,
15440
+ fadeIn: Math.round(fadeInSec * 10),
15441
+ fadeOut: Math.round(fadeOutSec * 10),
15252
15442
  mode,
15253
15443
  trackInstruments: trackInstMeta,
15254
15444
  trackCompression: trackCompMeta,
@@ -15270,6 +15460,8 @@ var mountDAW = (target, options = {}) => {
15270
15460
  reverbPreDelay,
15271
15461
  delay: delayAmount,
15272
15462
  delayDivision,
15463
+ fadeIn: Math.round(fadeInSec * 10),
15464
+ fadeOut: Math.round(fadeOutSec * 10),
15273
15465
  mode,
15274
15466
  trackInstruments: trackInstMeta,
15275
15467
  trackCompression: trackCompMeta,
@@ -15328,7 +15520,8 @@ var mountDAW = (target, options = {}) => {
15328
15520
  rev: t.vocalReverb,
15329
15521
  del: t.vocalDelay,
15330
15522
  gen: t.vocalGender,
15331
- bre: t.vocalBreathiness
15523
+ bre: t.vocalBreathiness,
15524
+ dbl: t.vocalOctaveDouble
15332
15525
  })).filter(
15333
15526
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
15334
15527
  ).map((x2) => {
@@ -15341,7 +15534,8 @@ var mountDAW = (target, options = {}) => {
15341
15534
  x2.rev === 0 ? "" : `r${x2.rev}`,
15342
15535
  x2.del === 0 ? "" : `e${x2.del}`,
15343
15536
  x2.gen === 50 ? "" : `g${x2.gen}`,
15344
- x2.bre === 50 ? "" : `h${x2.bre}`
15537
+ x2.bre === 50 ? "" : `h${x2.bre}`,
15538
+ x2.dbl ? "w1" : ""
15345
15539
  ].filter((s) => s.length > 0).join(" ");
15346
15540
  const head = params ? `${x2.model} ${params}` : x2.model;
15347
15541
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -15510,6 +15704,16 @@ var mountDAW = (target, options = {}) => {
15510
15704
  refs.delayDivision.value = delayDivision;
15511
15705
  options.onDelayDivisionChange?.(delayDivision);
15512
15706
  }
15707
+ if (meta.fadeIn !== void 0) {
15708
+ fadeInSec = meta.fadeIn / 10;
15709
+ refs.fadeIn.value = String(fadeInSec);
15710
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
15711
+ }
15712
+ if (meta.fadeOut !== void 0) {
15713
+ fadeOutSec = meta.fadeOut / 10;
15714
+ refs.fadeOut.value = String(fadeOutSec);
15715
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
15716
+ }
15513
15717
  }
15514
15718
  trackStates.forEach((t, i2) => {
15515
15719
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -15568,6 +15772,7 @@ var mountDAW = (target, options = {}) => {
15568
15772
  active.vocalDelay = 0;
15569
15773
  active.vocalGender = 50;
15570
15774
  active.vocalBreathiness = 50;
15775
+ active.vocalOctaveDouble = false;
15571
15776
  } else {
15572
15777
  for (const t of trackStates) {
15573
15778
  t.lyrics = "";
@@ -15581,6 +15786,7 @@ var mountDAW = (target, options = {}) => {
15581
15786
  t.vocalDelay = 0;
15582
15787
  t.vocalGender = 50;
15583
15788
  t.vocalBreathiness = 50;
15789
+ t.vocalOctaveDouble = false;
15584
15790
  }
15585
15791
  }
15586
15792
  lyrics?.forEach((lt) => {
@@ -15598,6 +15804,7 @@ var mountDAW = (target, options = {}) => {
15598
15804
  t.vocalDelay = lt.delay ?? 0;
15599
15805
  t.vocalGender = lt.gender ?? 50;
15600
15806
  t.vocalBreathiness = lt.breathiness ?? 50;
15807
+ t.vocalOctaveDouble = lt.octaveDouble ?? false;
15601
15808
  });
15602
15809
  for (const p of placements) {
15603
15810
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -15681,6 +15888,7 @@ var mountDAW = (target, options = {}) => {
15681
15888
  active.vocalDelay = 0;
15682
15889
  active.vocalGender = 50;
15683
15890
  active.vocalBreathiness = 50;
15891
+ active.vocalOctaveDouble = false;
15684
15892
  } else {
15685
15893
  clearAll();
15686
15894
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -15696,6 +15904,7 @@ var mountDAW = (target, options = {}) => {
15696
15904
  t.vocalDelay = 0;
15697
15905
  t.vocalGender = 50;
15698
15906
  t.vocalBreathiness = 50;
15907
+ t.vocalOctaveDouble = false;
15699
15908
  }
15700
15909
  }
15701
15910
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -15946,6 +16155,17 @@ var mountDAW = (target, options = {}) => {
15946
16155
  refs.delayAmountInfoBtn.addEventListener("click", () => {
15947
16156
  showModal("\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC", MASTER_DELAY_INFO_HTML);
15948
16157
  });
16158
+ refs.fadeIn.addEventListener("input", () => {
16159
+ fadeInSec = Number.parseFloat(refs.fadeIn.value) || 0;
16160
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
16161
+ });
16162
+ refs.fadeOut.addEventListener("input", () => {
16163
+ fadeOutSec = Number.parseFloat(refs.fadeOut.value) || 0;
16164
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
16165
+ });
16166
+ refs.fadeInfoBtn.addEventListener("click", () => {
16167
+ showModal("\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC", FADE_INFO_HTML);
16168
+ });
15949
16169
  refs.autoMasterInfoBtn.addEventListener("click", () => {
15950
16170
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15951
16171
  });
@@ -16223,6 +16443,20 @@ var mountDAW = (target, options = {}) => {
16223
16443
  redrawAll();
16224
16444
  })
16225
16445
  );
16446
+ refs.transposeApplyBtn.addEventListener(
16447
+ "click",
16448
+ () => overlayDuring(() => {
16449
+ transposeNotes(
16450
+ trackStates.map((t) => t.core),
16451
+ Number.parseInt(refs.transposeSelect.value, 10) || 0
16452
+ );
16453
+ redrawAll();
16454
+ updateUndoRedo();
16455
+ })
16456
+ );
16457
+ refs.transposeInfoBtn.addEventListener("click", () => {
16458
+ showModal("\u79FB\u8ABF\u306E\u89E3\u8AAC", TRANSPOSE_INFO_HTML);
16459
+ });
16226
16460
  if (showMidi) wireMidi();
16227
16461
  if (showMidiSearch) wireMidiSearch();
16228
16462
  document.addEventListener("keydown", onKeyDown);
@@ -17030,6 +17264,7 @@ var mountDAW = (target, options = {}) => {
17030
17264
  t.vocalDelay = data.vocalDelay ?? 0;
17031
17265
  t.vocalGender = data.vocalGender ?? 50;
17032
17266
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17267
+ t.vocalOctaveDouble = data.vocalOctaveDouble ?? false;
17033
17268
  },
17034
17269
  applyTrackInstrument: (trackIndex, instrumentName) => {
17035
17270
  const t = trackStates[trackIndex];
@@ -17151,6 +17386,7 @@ var playSingingMML = async (mml, options = {}) => {
17151
17386
  delaySend: (lt.delay ?? 0) / 100,
17152
17387
  gender: (lt.gender ?? 50) / 100,
17153
17388
  breathiness: (lt.breathiness ?? 50) / 100,
17389
+ octaveDouble: lt.octaveDouble,
17154
17390
  notes
17155
17391
  };
17156
17392
  });
@@ -18146,7 +18382,28 @@ var createDtmStudio = async (options = {}) => {
18146
18382
  safetyLimiter.attack.value = 1e-3;
18147
18383
  safetyLimiter.release.value = 0.1;
18148
18384
  finalMix.connect(safetyLimiter);
18149
- safetyLimiter.connect(options.destination ?? audioCtx.destination);
18385
+ const fadeGain = audioCtx.createGain();
18386
+ fadeGain.gain.value = 1;
18387
+ safetyLimiter.connect(fadeGain);
18388
+ fadeGain.connect(options.destination ?? audioCtx.destination);
18389
+ const scheduleFade = (params) => {
18390
+ fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
18391
+ if (!params) {
18392
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18393
+ return;
18394
+ }
18395
+ const { fadeInStartAt, fadeInEndAt, fadeOutStartAt, fadeOutEndAt } = params;
18396
+ if (fadeInStartAt !== void 0 && fadeInEndAt !== void 0) {
18397
+ fadeGain.gain.setValueAtTime(0, fadeInStartAt);
18398
+ fadeGain.gain.linearRampToValueAtTime(1, fadeInEndAt);
18399
+ } else {
18400
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18401
+ }
18402
+ if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
18403
+ fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
18404
+ fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
18405
+ }
18406
+ };
18150
18407
  const clipMeter = createClipMeter(audioCtx, finalMix);
18151
18408
  const channelStrips = /* @__PURE__ */ new Map();
18152
18409
  const getChannelStrip = (trackId) => {
@@ -18482,6 +18739,9 @@ var createDtmStudio = async (options = {}) => {
18482
18739
  delayDivision: options.delayDivision,
18483
18740
  onDelayDivisionChange: (division) => delayBus.setDivision(division),
18484
18741
  onBpmChange: (bpm) => delayBus.setBpm(bpm),
18742
+ fadeInSec: options.fadeInSec,
18743
+ fadeOutSec: options.fadeOutSec,
18744
+ onScheduleFade: scheduleFade,
18485
18745
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
18486
18746
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18487
18747
  onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
@@ -19177,5 +19437,6 @@ var createDtmStudio = async (options = {}) => {
19177
19437
  stripCustomVocals,
19178
19438
  stripLyrics,
19179
19439
  stripMmlMeta,
19440
+ transposeNotes,
19180
19441
  vocalVolumeToGain
19181
19442
  });