@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.mjs CHANGED
@@ -2189,8 +2189,9 @@ var parseLyrics = (mml) => {
2189
2189
  let delay = 0;
2190
2190
  let gender = 50;
2191
2191
  let breathiness = 50;
2192
+ let octaveDouble = false;
2192
2193
  const modelMatch = rest.match(
2193
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghe]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2194
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2194
2195
  );
2195
2196
  let model = "";
2196
2197
  const metaTokens = [];
@@ -2266,6 +2267,13 @@ var parseLyrics = (mml) => {
2266
2267
  rest = rest.substring(eMatch[0].length).trim();
2267
2268
  continue;
2268
2269
  }
2270
+ const wMatch = rest.match(/^w([01])/i);
2271
+ if (wMatch) {
2272
+ octaveDouble = wMatch[1] === "1";
2273
+ metaTokens.push(wMatch[0]);
2274
+ rest = rest.substring(wMatch[0].length).trim();
2275
+ continue;
2276
+ }
2269
2277
  break;
2270
2278
  }
2271
2279
  const lyricLines = [rest];
@@ -2285,6 +2293,7 @@ var parseLyrics = (mml) => {
2285
2293
  delay,
2286
2294
  gender,
2287
2295
  breathiness,
2296
+ octaveDouble,
2288
2297
  syllables,
2289
2298
  metaText: metaTokens.join(" "),
2290
2299
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2881,6 +2890,8 @@ var createKoeVoice = async (ctx, destination, options) => {
2881
2890
  var PREWARM_NOTES = 3;
2882
2891
  var STREAM_LOOKAHEAD_SEC = 1.5;
2883
2892
  var STREAM_POLL_MS = 100;
2893
+ var OCTAVE_DOUBLE_SEMITONES = -12;
2894
+ var OCTAVE_DOUBLE_PEAK_SCALE = 0.6;
2884
2895
  var FALLBACK_MODEL = "klatt";
2885
2896
  var createSingingVoices = (ctx, destination, options = {}) => {
2886
2897
  const catalog = {};
@@ -2967,7 +2978,24 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2967
2978
  forEachSungNote(track, (note, prevVowel) => {
2968
2979
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2969
2980
  n++;
2970
- tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
2981
+ tasks.push({
2982
+ model: m,
2983
+ note,
2984
+ prevVowel,
2985
+ pitch: note.pitch,
2986
+ vibrato: track.vibrato,
2987
+ expr
2988
+ });
2989
+ if (track.octaveDouble) {
2990
+ tasks.push({
2991
+ model: m,
2992
+ note,
2993
+ prevVowel,
2994
+ pitch: note.pitch + OCTAVE_DOUBLE_SEMITONES,
2995
+ vibrato: track.vibrato,
2996
+ expr
2997
+ });
2998
+ }
2971
2999
  });
2972
3000
  }
2973
3001
  const total = tasks.length;
@@ -2981,7 +3009,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2981
3009
  await (task.model.renderToCache?.(
2982
3010
  task.note.syllable,
2983
3011
  task.prevVowel,
2984
- task.note.pitch,
3012
+ task.pitch,
2985
3013
  task.note.durationSec * 1e3,
2986
3014
  task.vibrato,
2987
3015
  task.expr
@@ -3021,59 +3049,70 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3021
3049
  }
3022
3050
  if (opts?.isAudible && !opts.isAudible(track)) continue;
3023
3051
  const t0 = anchorTime + startSec;
3024
- if (model.renderToCache && model.scheduleCached) {
3025
- const renderToCache = model.renderToCache;
3026
- const scheduleCached = model.scheduleCached;
3027
- void (async () => {
3028
- const key = await renderToCache(
3029
- note.syllable,
3030
- prevVowel,
3031
- note.pitch,
3032
- note.durationSec * 1e3,
3033
- track.vibrato,
3034
- { gender: track.gender, breathiness: track.breathiness }
3035
- );
3036
- if (session !== streamSession) return;
3037
- if (key) {
3038
- const delay = ctx.currentTime - t0;
3039
- if (delay < 0.05) {
3040
- const dest = options.getTrackDestination?.(track.id ?? "");
3041
- const effPeak = dest ? peak * masterVolumeScalar : peak;
3042
- scheduleCached(
3043
- key,
3044
- t0,
3045
- effPeak,
3046
- track.pan,
3047
- track.reverbSend,
3048
- track.delaySend,
3049
- dest
3050
- );
3051
- opts?.onScheduled?.(track, note, t0);
3052
- } else {
3053
- console.warn(
3054
- `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3055
- );
3056
- opts?.onLateSkip?.(note, delay);
3052
+ const dispatchNote = (pitch, peakScale) => {
3053
+ if (model.renderToCache && model.scheduleCached) {
3054
+ const renderToCache = model.renderToCache;
3055
+ const scheduleCached = model.scheduleCached;
3056
+ void (async () => {
3057
+ const key = await renderToCache(
3058
+ note.syllable,
3059
+ prevVowel,
3060
+ pitch,
3061
+ note.durationSec * 1e3,
3062
+ track.vibrato,
3063
+ { gender: track.gender, breathiness: track.breathiness }
3064
+ );
3065
+ if (session !== streamSession) return;
3066
+ if (key) {
3067
+ const delay = ctx.currentTime - t0;
3068
+ if (delay < 0.05) {
3069
+ const dest = options.getTrackDestination?.(track.id ?? "");
3070
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3071
+ scheduleCached(
3072
+ key,
3073
+ t0,
3074
+ effPeak,
3075
+ track.pan,
3076
+ track.reverbSend,
3077
+ track.delaySend,
3078
+ dest
3079
+ );
3080
+ opts?.onScheduled?.(track, note, t0);
3081
+ } else {
3082
+ console.warn(
3083
+ `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
3084
+ );
3085
+ opts?.onLateSkip?.(note, delay);
3086
+ }
3057
3087
  }
3058
- }
3059
- })();
3060
- } else {
3061
- const when = t0 - ctx.currentTime;
3062
- const dest = options.getTrackDestination?.(track.id ?? "");
3063
- const effPeak = dest ? peak * masterVolumeScalar : peak;
3064
- model(note.syllable, {
3065
- trackId: track.id ?? "",
3066
- pitch: note.pitch,
3067
- velocity: 100,
3068
- volume: effPeak,
3069
- when,
3070
- duration: note.durationSec,
3071
- pan: track.pan,
3072
- reverbSend: track.reverbSend,
3073
- delaySend: track.delaySend,
3074
- destination: dest
3075
- });
3076
- opts?.onScheduled?.(track, note, t0);
3088
+ })();
3089
+ } else {
3090
+ const when = t0 - ctx.currentTime;
3091
+ const dest = options.getTrackDestination?.(track.id ?? "");
3092
+ const effPeak = (dest ? peak * masterVolumeScalar : peak) * peakScale;
3093
+ model(note.syllable, {
3094
+ trackId: track.id ?? "",
3095
+ pitch,
3096
+ velocity: 100,
3097
+ volume: effPeak,
3098
+ when,
3099
+ duration: note.durationSec,
3100
+ pan: track.pan,
3101
+ reverbSend: track.reverbSend,
3102
+ delaySend: track.delaySend,
3103
+ destination: dest
3104
+ });
3105
+ opts?.onScheduled?.(track, note, t0);
3106
+ }
3107
+ };
3108
+ dispatchNote(note.pitch, 1);
3109
+ if (track.octaveDouble) {
3110
+ dispatchNote(
3111
+ note.pitch + OCTAVE_DOUBLE_SEMITONES,
3112
+ OCTAVE_DOUBLE_PEAK_SCALE
3113
+ );
3114
+ }
3115
+ if (!(model.renderToCache && model.scheduleCached)) {
3077
3116
  await new Promise((resolve) => setTimeout(resolve, 0));
3078
3117
  }
3079
3118
  }
@@ -3132,7 +3171,7 @@ var PITCH_MAP = {
3132
3171
  b: 11
3133
3172
  };
3134
3173
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3135
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mode)=([\w-]+)/gi;
3174
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|fadein|fadeout|mode)=([\w-]+)/gi;
3136
3175
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3137
3176
  var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3138
3177
  var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
@@ -3166,6 +3205,12 @@ var parseMmlMeta = (mml) => {
3166
3205
  if (!Number.isNaN(dv)) meta.delay = clamp2(dv, 0, 100);
3167
3206
  } else if (key === "delaydiv") {
3168
3207
  if (["4", "8", "8d", "16"].includes(m[2])) meta.delayDivision = m[2];
3208
+ } else if (key === "fadein") {
3209
+ const fi = Number.parseInt(m[2], 10);
3210
+ if (!Number.isNaN(fi)) meta.fadeIn = clamp2(fi, 0, 100);
3211
+ } else if (key === "fadeout") {
3212
+ const fo = Number.parseInt(m[2], 10);
3213
+ if (!Number.isNaN(fo)) meta.fadeOut = clamp2(fo, 0, 100);
3169
3214
  } else if (key === "mode") {
3170
3215
  if (m[2] === "simple" || m[2] === "advanced") {
3171
3216
  meta.mode = m[2];
@@ -3241,6 +3286,10 @@ var formatMmlMeta = (meta, space = "") => {
3241
3286
  parts.push(`#delay=${meta.delay}`);
3242
3287
  if (meta.delayDivision && meta.delayDivision !== "8")
3243
3288
  parts.push(`#delaydiv=${meta.delayDivision}`);
3289
+ if (meta.fadeIn !== void 0 && meta.fadeIn !== 0)
3290
+ parts.push(`#fadein=${meta.fadeIn}`);
3291
+ if (meta.fadeOut !== void 0 && meta.fadeOut !== 0)
3292
+ parts.push(`#fadeout=${meta.fadeOut}`);
3244
3293
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3245
3294
  if (meta.trackInstruments) {
3246
3295
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
@@ -9110,6 +9159,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9110
9159
  delaySend: (lt.delay ?? 0) / 100,
9111
9160
  gender: (lt.gender ?? 50) / 100,
9112
9161
  breathiness: (lt.breathiness ?? 50) / 100,
9162
+ octaveDouble: lt.octaveDouble,
9113
9163
  notes
9114
9164
  };
9115
9165
  });
@@ -10987,6 +11037,17 @@ var buildUI = (target, options) => {
10987
11037
  </select>
10988
11038
  <button class="dtm-infobtn" data-dtm="delay-amount-info" title="\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10989
11039
  </div>
11040
+ <div class="dtm-row">
11041
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3</span>
11042
+ <input type="range" class="dtm-range dtm-grow" data-dtm="fade-in" value="0" min="0" max="10" step="0.5" aria-label="\u66F2\u982D\u306E\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u9577\uFF08\u79D2\uFF09">
11043
+ <span class="dtm-label" data-dtm="fade-in-label">0.0s</span>
11044
+ <button class="dtm-infobtn" data-dtm="fade-info" title="\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11045
+ </div>
11046
+ <div class="dtm-row">
11047
+ <span class="dtm-label">\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8</span>
11048
+ <input type="range" class="dtm-range dtm-grow" data-dtm="fade-out" value="0" min="0" max="10" step="0.5" aria-label="\u66F2\u5C3E\u306E\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u9577\uFF08\u79D2\uFF09">
11049
+ <span class="dtm-label" data-dtm="fade-out-label">0.0s</span>
11050
+ </div>
10990
11051
  </div>
10991
11052
  </details>
10992
11053
 
@@ -11071,6 +11132,25 @@ var buildUI = (target, options) => {
11071
11132
  </select>
11072
11133
  <button class="dtm-btn dtm-btn--primary" data-dtm="shift-apply">\u9069\u7528</button>
11073
11134
  </div>
11135
+ <div class="dtm-row">
11136
+ <span class="dtm-label">\u79FB\u8ABF</span>
11137
+ <select class="dtm-select" data-dtm="transpose-select" aria-label="\u79FB\u8ABF\u3059\u308B\u534A\u97F3\u6570">
11138
+ <option value="-12">-1oct</option>
11139
+ <option value="-7">-5th</option>
11140
+ <option value="-5">-4th</option>
11141
+ <option value="-3">-3\u534A\u97F3</option>
11142
+ <option value="-2">-2\u534A\u97F3</option>
11143
+ <option value="-1">-1\u534A\u97F3</option>
11144
+ <option value="1">+1\u534A\u97F3</option>
11145
+ <option value="2">+2\u534A\u97F3</option>
11146
+ <option value="3">+3\u534A\u97F3</option>
11147
+ <option value="5">+4th</option>
11148
+ <option value="7">+5th</option>
11149
+ <option value="12">+1oct</option>
11150
+ </select>
11151
+ <button class="dtm-btn dtm-btn--primary" data-dtm="transpose-apply">\u9069\u7528</button>
11152
+ <button class="dtm-infobtn" data-dtm="transpose-info" title="\u79FB\u8ABF\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
11153
+ </div>
11074
11154
  <div class="dtm-row">
11075
11155
  <button class="dtm-btn dtm-btn--danger" data-dtm="macro-clear">\u5168\u6D88\u53BB</button>
11076
11156
  <button class="dtm-btn dtm-btn--accent" data-dtm="macro-random">\u30E9\u30F3\u30C0\u30E0\u914D\u7F6E</button>
@@ -11193,6 +11273,11 @@ var buildUI = (target, options) => {
11193
11273
  delayAmountLabel: sel("delay-amount-label"),
11194
11274
  delayAmountInfoBtn: sel("delay-amount-info"),
11195
11275
  delayDivision: sel("delay-division"),
11276
+ fadeIn: sel("fade-in"),
11277
+ fadeInLabel: sel("fade-in-label"),
11278
+ fadeOut: sel("fade-out"),
11279
+ fadeOutLabel: sel("fade-out-label"),
11280
+ fadeInfoBtn: sel("fade-info"),
11196
11281
  autoMasterBtn: sel("auto-master"),
11197
11282
  autoMasterInfoBtn: sel("auto-master-info"),
11198
11283
  trackTabs: sel("track-tabs"),
@@ -11213,6 +11298,9 @@ var buildUI = (target, options) => {
11213
11298
  applyActiveOnly: sel("apply-active-only"),
11214
11299
  shiftSelect: sel("shift-select"),
11215
11300
  shiftApplyBtn: sel("shift-apply"),
11301
+ transposeSelect: sel("transpose-select"),
11302
+ transposeApplyBtn: sel("transpose-apply"),
11303
+ transposeInfoBtn: sel("transpose-info"),
11216
11304
  macroClear: sel("macro-clear"),
11217
11305
  macroRandom: sel("macro-random"),
11218
11306
  macroHarmonic: sel("macro-harmonic"),
@@ -11467,6 +11555,18 @@ var shiftNotes = (cores, shiftSteps) => {
11467
11555
  }
11468
11556
  }
11469
11557
  };
11558
+ var transposeNotes = (cores, semitones) => {
11559
+ if (semitones === 0) return;
11560
+ for (const core of cores) {
11561
+ const notes = [...core.getNotes()];
11562
+ for (const note of notes) {
11563
+ const newPitch = Math.max(0, Math.min(127, note.pitch + semitones));
11564
+ if (newPitch !== note.pitch) {
11565
+ core.moveNote(note.id, note.startStep, newPitch);
11566
+ }
11567
+ }
11568
+ }
11569
+ };
11470
11570
 
11471
11571
  // src/midi-io.ts
11472
11572
  var STEPS_PER_BEAT3 = 48;
@@ -12899,6 +12999,16 @@ var VIBRATO_INFO_HTML = `
12899
12999
  <p>\u30D4\u30C3\u30C1\u3092\u63FA\u3089\u3059\u52B9\u679C\u306A\u306E\u3067\u3001\u58F0\u8CEA\u305D\u306E\u3082\u306E\u3092\u5909\u3048\u308B\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306F\u72EC\u7ACB\u3057\u3066\u7D44\u307F\u5408\u308F\u305B\u3089\u308C\u307E\u3059\u3002\u901F\u3055\u30FB\u6DF1\u3055\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\uFF08\u6B4C\u3068\u3057\u3066\u7834\u7DBB\u3057\u306B\u304F\u3044\u63A7\u3048\u3081\u306A\u91CF\u306B\u56FA\u5B9A\uFF09\u3002\u66F2\u3084\u7B87\u6240\u3054\u3068\u306B\u639B\u3051\u305F\u3044/\u639B\u3051\u305F\u304F\u306A\u3044\u304C\u3042\u308B\u5834\u5408\u306F\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u6B4C\u8A5E\u3092\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002</p>
12900
13000
  </div>
12901
13001
  `;
13002
+ var OCTAVE_DOUBLE_INFO_HTML = `
13003
+ <div class="dtm-modal-body-content">
13004
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13005
+ <p>ON\u306B\u3059\u308B\u3068\u3001\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u5404\u97F3\u7BC0\u30921\u30AA\u30AF\u30BF\u30FC\u30D6\u4E0B\uFF08\u63A7\u3048\u3081\u306A\u97F3\u91CF\uFF09\u3067\u540C\u6642\u306B\u91CD\u306D\u3066\u767A\u97F3\u3057\u307E\u3059\u3002\u58F0\u306B\u539A\u307F\u30FB\u30D1\u30EF\u30FC\u3092\u8DB3\u3059\u300C\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EA\u30F3\u30B0\u300D\u3068\u3044\u3046\u5B9A\u756A\u306E\u30DC\u30FC\u30AB\u30EB\u52A0\u5DE5\u3067\u3059\u3002</p>
13006
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13007
+ <p>\u30B5\u30D3\u306E\u6C7A\u3081\u306E\u30D5\u30EC\u30FC\u30BA\u3001\u30ED\u30DC\u30C3\u30C8\u7684/\u30C0\u30FC\u30AF\u306A\u8CEA\u611F\u306E\u6F14\u51FA\u3001\u30E6\u30CB\u30BE\u30F3\u30CF\u30FC\u30E2\u30CB\u30FC\u306A\u3069\u3067\u3088\u304F\u4F7F\u308F\u308C\u307E\u3059\u3002\u66F2\u5168\u4F53\u306B\u639B\u3051\u3063\u3071\u306A\u3057\u306B\u3059\u308B\u3068\u5E38\u306B\u304F\u3069\u3044\u5370\u8C61\u306B\u306A\u308A\u3084\u3059\u3044\u306E\u3067\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u4F7F\u3044\u305F\u3044\u7B87\u6240\u3060\u3051\u306B\u7D5E\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
13008
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13009
+ <p>\u91CD\u306D\u308B\u58F0\u306B\u3082\u540C\u3058\u30D3\u30D6\u30E9\u30FC\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9/\u30EA\u30D0\u30FC\u30D6\u9001\u308A/\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u306E\u8A2D\u5B9A\u304C\u305D\u306E\u307E\u307E\u9069\u7528\u3055\u308C\u307E\u3059\u3002\u97F3\u91CF\u6BD4\u30FB\u97F3\u7A0B\u5DEE\uFF081\u30AA\u30AF\u30BF\u30FC\u30D6\u56FA\u5B9A\uFF09\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\u3002\u5408\u6210\u304C\u5358\u7D14\u306B2\u500D\u8D70\u308B\u305F\u3081\u3001\u97F3\u6570\u306E\u591A\u3044\u30C8\u30E9\u30C3\u30AF\u3067\u306F\u3084\u3084\u91CD\u304F\u306A\u308A\u307E\u3059\u3002</p>
13010
+ </div>
13011
+ `;
12902
13012
  var GENDER_INFO_HTML = `
12903
13013
  <div class="dtm-modal-body-content">
12904
13014
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -12961,6 +13071,26 @@ var MASTER_DELAY_INFO_HTML = `
12961
13071
  <p>\u30EA\u30D0\u30FC\u30D6\u3068\u540C\u3058\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u500B\u5225\u306E\u300C\u30C7\u30A3\u30EC\u30A4\u9001\u308A\u300D\u304C0%\u306A\u3089\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u4E0A\u3052\u3066\u3082\u7121\u97F3\u3001\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002</p>
12962
13072
  </div>
12963
13073
  `;
13074
+ var FADE_INFO_HTML = `
13075
+ <div class="dtm-modal-body-content">
13076
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13077
+ <p>\u66F2\u306E\u982D\u3067\u97F3\u91CF0%\u304B\u3089\u5F90\u3005\u306B\u4E0A\u3052\u308B\u300C\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u300D\u3001\u66F2\u306E\u7D42\u308F\u308A\u3067\u97F3\u91CF\u30920%\u307E\u3067\u5F90\u3005\u306B\u4E0B\u3052\u308B\u300C\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u300D\u3092\u8A2D\u5B9A\u3057\u307E\u3059\u30020\u79D2\u3067\u30D5\u30A7\u30FC\u30C9\u306A\u3057\u3067\u3059\u3002</p>
13078
+ <h4>\u3044\u3064\u52B9\u304F\u304B</h4>
13079
+ <p>\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3\u306F\u66F2\u306E\u5148\u982D\uFF080\u5C0F\u7BC0\u76EE\uFF09\u304B\u3089\u518D\u751F\u3057\u305F\u3068\u304D\u3060\u3051\u639B\u304B\u308A\u307E\u3059\u3002\u9014\u4E2D\u306E\u5C0F\u7BC0\u304B\u3089\u518D\u751F\u30FB\u30B7\u30FC\u30AF\u3057\u305F\u5834\u5408\u306F\u639B\u304B\u308A\u307E\u305B\u3093\u3002\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8\u306F\u3001\u4ECA\u30CE\u30FC\u30C8\u304C\u7F6E\u304B\u308C\u3066\u3044\u308B\u7BC4\u56F2\u306E\u7D42\u7AEF\u306B\u5411\u3051\u3066\u81EA\u52D5\u7684\u306B\u639B\u304B\u308A\u307E\u3059\uFF08\u30CE\u30FC\u30C8\u3092\u8DB3\u305B\u3070\u7D42\u7AEF\u3082\u4F38\u3073\u307E\u3059\uFF09\u3002</p>
13080
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13081
+ <p>\u7DE8\u96C6\u4E2D\u306F\u307B\u3068\u3093\u3069\u610F\u8B58\u3057\u307E\u305B\u3093\u304C\u3001\u66F8\u304D\u51FA\u3057\u3066\u4EBA\u306B\u8074\u304B\u305B\u308B\u30FB\u914D\u4FE1\u3059\u308B\u6BB5\u306B\u306A\u308B\u3068\u3001\u66F2\u306E\u51FA\u5165\u308A\u304C\u5510\u7A81\u3060\u3068\u7D20\u4EBA\u3063\u307D\u304F\u805E\u3053\u3048\u304C\u3061\u3067\u3059\u3002\u6570\u79D2\u306E\u30D5\u30A7\u30FC\u30C9\u3092\u5165\u308C\u308B\u3060\u3051\u3067\u4ED5\u4E0A\u304C\u308A\u306E\u5370\u8C61\u304C\u5909\u308F\u308A\u307E\u3059\u3002</p>
13082
+ </div>
13083
+ `;
13084
+ var TRANSPOSE_INFO_HTML = `
13085
+ <div class="dtm-modal-body-content">
13086
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13087
+ <p>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30CE\u30FC\u30C8\u3092\u9078\u3093\u3060\u534A\u97F3\u6570\u3060\u3051\u4E00\u62EC\u3067\u79FB\u8ABF\u3057\u307E\u3059\u3002\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306E\u6B4C\u58F0\u3082\u30CE\u30FC\u30C8\u306E\u30D4\u30C3\u30C1\u306B\u8FFD\u5F93\u3059\u308B\u305F\u3081\u3001\u30DC\u30FC\u30AB\u30EB\u3082\u4E00\u7DD2\u306B\u79FB\u8ABF\u3055\u308C\u307E\u3059\u3002</p>
13088
+ <h4>\u4F7F\u3044\u3069\u3053\u308D</h4>
13089
+ <p>\u300C\u6B4C\u3063\u3066\u307F\u305F\u3089\u97F3\u57DF\u304C\u9AD8\u3059\u304E\u305F/\u4F4E\u3059\u304E\u305F\u300D\u3068\u3044\u3046\u3068\u304D\u306E\u8ABF\u6574\u5B9A\u756A\u3067\u3059\u3002\u30DC\u30FC\u30AB\u30EB\u306E\u58F0\u57DF\u306B\u5408\u308F\u305B\u3066\u66F2\u5168\u4F53\u306E\u30AD\u30FC\u3092\u4E0A\u3052\u4E0B\u3052\u3067\u304D\u307E\u3059\u3002</p>
13090
+ <h4>\u6CE8\u610F</h4>
13091
+ <p>\u30CE\u30FC\u30C8\u30C7\u30FC\u30BF\u3092\u76F4\u63A5\u66F8\u304D\u63DB\u3048\u308B\u64CD\u4F5C\u3067\u3059\uFF08\u5143\u306B\u623B\u3059\u306B\u306F\u300C\u5143\u306B\u623B\u3059\u300D\u30DC\u30BF\u30F3\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\uFF09\u3002MIDI\u30CE\u30FC\u30C8\u7BC4\u56F2\uFF080-127\uFF09\u3092\u8D85\u3048\u308B\u97F3\u306F\u7BC4\u56F2\u5185\u306B\u30AF\u30E9\u30F3\u30D7\u3055\u308C\u307E\u3059\u3002</p>
13092
+ </div>
13093
+ `;
12964
13094
  var TRACK_EQ_INFO_HTML = `
12965
13095
  <div class="dtm-modal-body-content">
12966
13096
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -13296,6 +13426,10 @@ var mountDAW = (target, options = {}) => {
13296
13426
  refs.delayAmount.value = String(options.delayAmount ?? 0);
13297
13427
  refs.delayAmountLabel.textContent = `${options.delayAmount ?? 0}%`;
13298
13428
  refs.delayDivision.value = options.delayDivision ?? "8";
13429
+ refs.fadeIn.value = String(options.fadeInSec ?? 0);
13430
+ refs.fadeInLabel.textContent = `${(options.fadeInSec ?? 0).toFixed(1)}s`;
13431
+ refs.fadeOut.value = String(options.fadeOutSec ?? 0);
13432
+ refs.fadeOutLabel.textContent = `${(options.fadeOutSec ?? 0).toFixed(1)}s`;
13299
13433
  refs.drumVolume.value = String(options.drumVolume ?? 80);
13300
13434
  refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
13301
13435
  const renderConfig = {
@@ -13316,6 +13450,8 @@ var mountDAW = (target, options = {}) => {
13316
13450
  let reverbPreDelay = options.reverbPreDelay ?? DEFAULT_REVERB_PREDELAY_MS;
13317
13451
  let delayAmount = options.delayAmount ?? 0;
13318
13452
  let delayDivision = options.delayDivision ?? "8";
13453
+ let fadeInSec = options.fadeInSec ?? 0;
13454
+ let fadeOutSec = options.fadeOutSec ?? 0;
13319
13455
  let unsubscribeClip;
13320
13456
  options.onReverbChange?.(reverbAmount);
13321
13457
  options.onReverbDecayChange?.(reverbDecay);
@@ -13404,7 +13540,8 @@ var mountDAW = (target, options = {}) => {
13404
13540
  vocalReverb: t.vocalReverb,
13405
13541
  vocalDelay: t.vocalDelay,
13406
13542
  vocalGender: t.vocalGender,
13407
- vocalBreathiness: t.vocalBreathiness
13543
+ vocalBreathiness: t.vocalBreathiness,
13544
+ vocalOctaveDouble: t.vocalOctaveDouble
13408
13545
  };
13409
13546
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13410
13547
  lyricsDebounceTimer = setTimeout(() => {
@@ -13469,6 +13606,7 @@ var mountDAW = (target, options = {}) => {
13469
13606
  vocalDelay: 0,
13470
13607
  vocalGender: 50,
13471
13608
  vocalBreathiness: 50,
13609
+ vocalOctaveDouble: false,
13472
13610
  trackInstrument: "",
13473
13611
  trackCompression: 0,
13474
13612
  trackWidth: 100,
@@ -13498,6 +13636,7 @@ var mountDAW = (target, options = {}) => {
13498
13636
  delay: t.vocalDelay,
13499
13637
  gender: t.vocalGender,
13500
13638
  breathiness: t.vocalBreathiness,
13639
+ octaveDouble: t.vocalOctaveDouble,
13501
13640
  syllables
13502
13641
  });
13503
13642
  });
@@ -13505,16 +13644,18 @@ var mountDAW = (target, options = {}) => {
13505
13644
  };
13506
13645
  const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
13507
13646
  let showModal;
13508
- const getMaxNoteStep = () => {
13647
+ const getSongEndStepExact = () => {
13509
13648
  let maxEndStep = 0;
13510
13649
  for (const t of trackStates) {
13511
13650
  for (const n of t.core.getNotes()) {
13512
13651
  const end = n.startStep + n.durationSteps;
13513
- if (end > maxEndStep) {
13514
- maxEndStep = end;
13515
- }
13652
+ if (end > maxEndStep) maxEndStep = end;
13516
13653
  }
13517
13654
  }
13655
+ return maxEndStep;
13656
+ };
13657
+ const getMaxNoteStep = () => {
13658
+ const maxEndStep = getSongEndStepExact();
13518
13659
  if (maxEndStep === 0) {
13519
13660
  return renderConfig.stepsPerBar * 4;
13520
13661
  }
@@ -14305,6 +14446,7 @@ var mountDAW = (target, options = {}) => {
14305
14446
  delaySend: (lt.delay ?? 0) / 100,
14306
14447
  gender: (lt.gender ?? 50) / 100,
14307
14448
  breathiness: (lt.breathiness ?? 50) / 100,
14449
+ octaveDouble: lt.octaveDouble,
14308
14450
  notes
14309
14451
  };
14310
14452
  }) : [];
@@ -14341,6 +14483,29 @@ var mountDAW = (target, options = {}) => {
14341
14483
  }
14342
14484
  playbackState = "playing";
14343
14485
  sequencer.start(fromStep);
14486
+ {
14487
+ const anchor = sequencer.getStartTime();
14488
+ const secondsPerStepFade = 60 / bpm / 48;
14489
+ const params = {};
14490
+ if (fadeInSec > 0 && fromStep === 0) {
14491
+ params.fadeInStartAt = anchor;
14492
+ params.fadeInEndAt = anchor + fadeInSec;
14493
+ }
14494
+ if (fadeOutSec > 0) {
14495
+ const endStep = getSongEndStepExact();
14496
+ const totalDurationSec = (endStep - fromStep) * secondsPerStepFade;
14497
+ if (totalDurationSec > 0) {
14498
+ const fadeOutEndAt = anchor + totalDurationSec;
14499
+ const earliestStart = params.fadeInEndAt ?? anchor;
14500
+ params.fadeOutEndAt = fadeOutEndAt;
14501
+ params.fadeOutStartAt = Math.max(
14502
+ fadeOutEndAt - fadeOutSec,
14503
+ earliestStart
14504
+ );
14505
+ }
14506
+ }
14507
+ options.onScheduleFade?.(params);
14508
+ }
14344
14509
  if (streaming && voices) {
14345
14510
  voices.startStream(streamTracks, sequencer.getStartTime(), {
14346
14511
  isAudible: (t) => !isSolo || t.id === activeTrackId,
@@ -14358,6 +14523,7 @@ var mountDAW = (target, options = {}) => {
14358
14523
  sequencer.stop();
14359
14524
  options.singingVoices?.stopStream();
14360
14525
  clearSoundTimers();
14526
+ options.onScheduleFade?.(null);
14361
14527
  playbackState = "paused";
14362
14528
  updateTransport();
14363
14529
  };
@@ -14371,6 +14537,7 @@ var mountDAW = (target, options = {}) => {
14371
14537
  sequencer.stop();
14372
14538
  options.singingVoices?.stopStream();
14373
14539
  clearSoundTimers();
14540
+ options.onScheduleFade?.(null);
14374
14541
  playbackState = "stopped";
14375
14542
  currentPlayStep = 0;
14376
14543
  updateTransport();
@@ -14680,6 +14847,12 @@ var mountDAW = (target, options = {}) => {
14680
14847
  </span>
14681
14848
  <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14682
14849
  </div>
14850
+ <div class="dtm-row">
14851
+ <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14852
+ <input type="checkbox" data-dtm="lyric-octave-double" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB">\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB
14853
+ </span>
14854
+ <button class="dtm-infobtn" data-dtm="lyric-octave-double-info" title="\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14855
+ </div>
14683
14856
  </details>
14684
14857
  <textarea class="dtm-textarea" data-dtm="lyric-input" rows="2" placeholder="\u3072\u3089\u304C\u306A\u30FB\u30AB\u30BF\u30AB\u30CA\u3067\u6B4C\u8A5E\uFF08\u4F8B: \u3069\u308C\u307F\u3075\u3041\u305D\u3089\u3057\u3069\uFF09"></textarea>
14685
14858
  </div>`;
@@ -14771,6 +14944,15 @@ var mountDAW = (target, options = {}) => {
14771
14944
  lyricVibratoInfo.addEventListener("click", () => {
14772
14945
  showModal("\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u89E3\u8AAC", VIBRATO_INFO_HTML);
14773
14946
  });
14947
+ const lyricOctaveDouble = lyricDiv.querySelector(
14948
+ '[data-dtm="lyric-octave-double"]'
14949
+ );
14950
+ const lyricOctaveDoubleInfo = lyricDiv.querySelector(
14951
+ '[data-dtm="lyric-octave-double-info"]'
14952
+ );
14953
+ lyricOctaveDoubleInfo.addEventListener("click", () => {
14954
+ showModal("\u30AA\u30AF\u30BF\u30FC\u30D6\u30C0\u30D6\u30EB\u89E3\u8AAC", OCTAVE_DOUBLE_INFO_HTML);
14955
+ });
14774
14956
  const lyricTerms = lyricDiv.querySelector(
14775
14957
  '[data-dtm="lyric-terms"]'
14776
14958
  );
@@ -14841,6 +15023,7 @@ var mountDAW = (target, options = {}) => {
14841
15023
  lyricBreathiness.value = String(active.vocalBreathiness);
14842
15024
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14843
15025
  lyricVibrato.checked = active.vocalVibrato;
15026
+ lyricOctaveDouble.checked = active.vocalOctaveDouble;
14844
15027
  const updateLyricCount = () => {
14845
15028
  const n = normalizeLyrics(lyricInput.value).length;
14846
15029
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
@@ -14962,6 +15145,10 @@ var mountDAW = (target, options = {}) => {
14962
15145
  active.vocalVibrato = lyricVibrato.checked;
14963
15146
  fireLyricsChange(active);
14964
15147
  });
15148
+ lyricOctaveDouble.addEventListener("change", () => {
15149
+ active.vocalOctaveDouble = lyricOctaveDouble.checked;
15150
+ fireLyricsChange(active);
15151
+ });
14965
15152
  lyricInput.addEventListener("input", () => {
14966
15153
  active.lyrics = lyricInput.value;
14967
15154
  updateLyricCount();
@@ -15119,6 +15306,8 @@ var mountDAW = (target, options = {}) => {
15119
15306
  reverbPreDelay,
15120
15307
  delay: delayAmount,
15121
15308
  delayDivision,
15309
+ fadeIn: Math.round(fadeInSec * 10),
15310
+ fadeOut: Math.round(fadeOutSec * 10),
15122
15311
  mode,
15123
15312
  trackInstruments: trackInstMeta,
15124
15313
  trackCompression: trackCompMeta,
@@ -15140,6 +15329,8 @@ var mountDAW = (target, options = {}) => {
15140
15329
  reverbPreDelay,
15141
15330
  delay: delayAmount,
15142
15331
  delayDivision,
15332
+ fadeIn: Math.round(fadeInSec * 10),
15333
+ fadeOut: Math.round(fadeOutSec * 10),
15143
15334
  mode,
15144
15335
  trackInstruments: trackInstMeta,
15145
15336
  trackCompression: trackCompMeta,
@@ -15198,7 +15389,8 @@ var mountDAW = (target, options = {}) => {
15198
15389
  rev: t.vocalReverb,
15199
15390
  del: t.vocalDelay,
15200
15391
  gen: t.vocalGender,
15201
- bre: t.vocalBreathiness
15392
+ bre: t.vocalBreathiness,
15393
+ dbl: t.vocalOctaveDouble
15202
15394
  })).filter(
15203
15395
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
15204
15396
  ).map((x2) => {
@@ -15211,7 +15403,8 @@ var mountDAW = (target, options = {}) => {
15211
15403
  x2.rev === 0 ? "" : `r${x2.rev}`,
15212
15404
  x2.del === 0 ? "" : `e${x2.del}`,
15213
15405
  x2.gen === 50 ? "" : `g${x2.gen}`,
15214
- x2.bre === 50 ? "" : `h${x2.bre}`
15406
+ x2.bre === 50 ? "" : `h${x2.bre}`,
15407
+ x2.dbl ? "w1" : ""
15215
15408
  ].filter((s) => s.length > 0).join(" ");
15216
15409
  const head = params ? `${x2.model} ${params}` : x2.model;
15217
15410
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -15380,6 +15573,16 @@ var mountDAW = (target, options = {}) => {
15380
15573
  refs.delayDivision.value = delayDivision;
15381
15574
  options.onDelayDivisionChange?.(delayDivision);
15382
15575
  }
15576
+ if (meta.fadeIn !== void 0) {
15577
+ fadeInSec = meta.fadeIn / 10;
15578
+ refs.fadeIn.value = String(fadeInSec);
15579
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
15580
+ }
15581
+ if (meta.fadeOut !== void 0) {
15582
+ fadeOutSec = meta.fadeOut / 10;
15583
+ refs.fadeOut.value = String(fadeOutSec);
15584
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
15585
+ }
15383
15586
  }
15384
15587
  trackStates.forEach((t, i2) => {
15385
15588
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -15438,6 +15641,7 @@ var mountDAW = (target, options = {}) => {
15438
15641
  active.vocalDelay = 0;
15439
15642
  active.vocalGender = 50;
15440
15643
  active.vocalBreathiness = 50;
15644
+ active.vocalOctaveDouble = false;
15441
15645
  } else {
15442
15646
  for (const t of trackStates) {
15443
15647
  t.lyrics = "";
@@ -15451,6 +15655,7 @@ var mountDAW = (target, options = {}) => {
15451
15655
  t.vocalDelay = 0;
15452
15656
  t.vocalGender = 50;
15453
15657
  t.vocalBreathiness = 50;
15658
+ t.vocalOctaveDouble = false;
15454
15659
  }
15455
15660
  }
15456
15661
  lyrics?.forEach((lt) => {
@@ -15468,6 +15673,7 @@ var mountDAW = (target, options = {}) => {
15468
15673
  t.vocalDelay = lt.delay ?? 0;
15469
15674
  t.vocalGender = lt.gender ?? 50;
15470
15675
  t.vocalBreathiness = lt.breathiness ?? 50;
15676
+ t.vocalOctaveDouble = lt.octaveDouble ?? false;
15471
15677
  });
15472
15678
  for (const p of placements) {
15473
15679
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -15551,6 +15757,7 @@ var mountDAW = (target, options = {}) => {
15551
15757
  active.vocalDelay = 0;
15552
15758
  active.vocalGender = 50;
15553
15759
  active.vocalBreathiness = 50;
15760
+ active.vocalOctaveDouble = false;
15554
15761
  } else {
15555
15762
  clearAll();
15556
15763
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -15566,6 +15773,7 @@ var mountDAW = (target, options = {}) => {
15566
15773
  t.vocalDelay = 0;
15567
15774
  t.vocalGender = 50;
15568
15775
  t.vocalBreathiness = 50;
15776
+ t.vocalOctaveDouble = false;
15569
15777
  }
15570
15778
  }
15571
15779
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -15816,6 +16024,17 @@ var mountDAW = (target, options = {}) => {
15816
16024
  refs.delayAmountInfoBtn.addEventListener("click", () => {
15817
16025
  showModal("\u30DE\u30B9\u30BF\u30C7\u30A3\u30EC\u30A4\u306E\u89E3\u8AAC", MASTER_DELAY_INFO_HTML);
15818
16026
  });
16027
+ refs.fadeIn.addEventListener("input", () => {
16028
+ fadeInSec = Number.parseFloat(refs.fadeIn.value) || 0;
16029
+ refs.fadeInLabel.textContent = `${fadeInSec.toFixed(1)}s`;
16030
+ });
16031
+ refs.fadeOut.addEventListener("input", () => {
16032
+ fadeOutSec = Number.parseFloat(refs.fadeOut.value) || 0;
16033
+ refs.fadeOutLabel.textContent = `${fadeOutSec.toFixed(1)}s`;
16034
+ });
16035
+ refs.fadeInfoBtn.addEventListener("click", () => {
16036
+ showModal("\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3/\u30A2\u30A6\u30C8\u306E\u89E3\u8AAC", FADE_INFO_HTML);
16037
+ });
15819
16038
  refs.autoMasterInfoBtn.addEventListener("click", () => {
15820
16039
  showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15821
16040
  });
@@ -16093,6 +16312,20 @@ var mountDAW = (target, options = {}) => {
16093
16312
  redrawAll();
16094
16313
  })
16095
16314
  );
16315
+ refs.transposeApplyBtn.addEventListener(
16316
+ "click",
16317
+ () => overlayDuring(() => {
16318
+ transposeNotes(
16319
+ trackStates.map((t) => t.core),
16320
+ Number.parseInt(refs.transposeSelect.value, 10) || 0
16321
+ );
16322
+ redrawAll();
16323
+ updateUndoRedo();
16324
+ })
16325
+ );
16326
+ refs.transposeInfoBtn.addEventListener("click", () => {
16327
+ showModal("\u79FB\u8ABF\u306E\u89E3\u8AAC", TRANSPOSE_INFO_HTML);
16328
+ });
16096
16329
  if (showMidi) wireMidi();
16097
16330
  if (showMidiSearch) wireMidiSearch();
16098
16331
  document.addEventListener("keydown", onKeyDown);
@@ -16900,6 +17133,7 @@ var mountDAW = (target, options = {}) => {
16900
17133
  t.vocalDelay = data.vocalDelay ?? 0;
16901
17134
  t.vocalGender = data.vocalGender ?? 50;
16902
17135
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17136
+ t.vocalOctaveDouble = data.vocalOctaveDouble ?? false;
16903
17137
  },
16904
17138
  applyTrackInstrument: (trackIndex, instrumentName) => {
16905
17139
  const t = trackStates[trackIndex];
@@ -17021,6 +17255,7 @@ var playSingingMML = async (mml, options = {}) => {
17021
17255
  delaySend: (lt.delay ?? 0) / 100,
17022
17256
  gender: (lt.gender ?? 50) / 100,
17023
17257
  breathiness: (lt.breathiness ?? 50) / 100,
17258
+ octaveDouble: lt.octaveDouble,
17024
17259
  notes
17025
17260
  };
17026
17261
  });
@@ -18015,7 +18250,28 @@ var createDtmStudio = async (options = {}) => {
18015
18250
  safetyLimiter.attack.value = 1e-3;
18016
18251
  safetyLimiter.release.value = 0.1;
18017
18252
  finalMix.connect(safetyLimiter);
18018
- safetyLimiter.connect(options.destination ?? audioCtx.destination);
18253
+ const fadeGain = audioCtx.createGain();
18254
+ fadeGain.gain.value = 1;
18255
+ safetyLimiter.connect(fadeGain);
18256
+ fadeGain.connect(options.destination ?? audioCtx.destination);
18257
+ const scheduleFade = (params) => {
18258
+ fadeGain.gain.cancelScheduledValues(audioCtx.currentTime);
18259
+ if (!params) {
18260
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18261
+ return;
18262
+ }
18263
+ const { fadeInStartAt, fadeInEndAt, fadeOutStartAt, fadeOutEndAt } = params;
18264
+ if (fadeInStartAt !== void 0 && fadeInEndAt !== void 0) {
18265
+ fadeGain.gain.setValueAtTime(0, fadeInStartAt);
18266
+ fadeGain.gain.linearRampToValueAtTime(1, fadeInEndAt);
18267
+ } else {
18268
+ fadeGain.gain.setValueAtTime(1, audioCtx.currentTime);
18269
+ }
18270
+ if (fadeOutStartAt !== void 0 && fadeOutEndAt !== void 0) {
18271
+ fadeGain.gain.setValueAtTime(1, fadeOutStartAt);
18272
+ fadeGain.gain.linearRampToValueAtTime(0, fadeOutEndAt);
18273
+ }
18274
+ };
18019
18275
  const clipMeter = createClipMeter(audioCtx, finalMix);
18020
18276
  const channelStrips = /* @__PURE__ */ new Map();
18021
18277
  const getChannelStrip = (trackId) => {
@@ -18351,6 +18607,9 @@ var createDtmStudio = async (options = {}) => {
18351
18607
  delayDivision: options.delayDivision,
18352
18608
  onDelayDivisionChange: (division) => delayBus.setDivision(division),
18353
18609
  onBpmChange: (bpm) => delayBus.setBpm(bpm),
18610
+ fadeInSec: options.fadeInSec,
18611
+ fadeOutSec: options.fadeOutSec,
18612
+ onScheduleFade: scheduleFade,
18354
18613
  onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
18355
18614
  onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
18356
18615
  onTrackEqLowChange: (trackId, db) => getChannelStrip(trackId).setEqLow(db),
@@ -19045,5 +19304,6 @@ export {
19045
19304
  stripCustomVocals,
19046
19305
  stripLyrics,
19047
19306
  stripMmlMeta,
19307
+ transposeNotes,
19048
19308
  vocalVolumeToGain
19049
19309
  };