@onjmin/dtm 0.1.84 → 0.1.85

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
@@ -2316,8 +2316,10 @@ var parseLyrics = (mml) => {
2316
2316
  let octave = 0;
2317
2317
  let vibrato = false;
2318
2318
  let reverb = 0;
2319
+ let gender = 50;
2320
+ let breathiness = 50;
2319
2321
  const modelMatch = rest.match(
2320
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobr]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2322
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrgh]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2321
2323
  );
2322
2324
  let model = "";
2323
2325
  const metaTokens = [];
@@ -2372,6 +2374,20 @@ var parseLyrics = (mml) => {
2372
2374
  rest = rest.substring(rMatch[0].length).trim();
2373
2375
  continue;
2374
2376
  }
2377
+ const gMatch = rest.match(/^g(\d+)/i);
2378
+ if (gMatch) {
2379
+ gender = clamp(Number.parseInt(gMatch[1], 10), 0, 100);
2380
+ metaTokens.push(gMatch[0]);
2381
+ rest = rest.substring(gMatch[0].length).trim();
2382
+ continue;
2383
+ }
2384
+ const hMatch = rest.match(/^h(\d+)/i);
2385
+ if (hMatch) {
2386
+ breathiness = clamp(Number.parseInt(hMatch[1], 10), 0, 100);
2387
+ metaTokens.push(hMatch[0]);
2388
+ rest = rest.substring(hMatch[0].length).trim();
2389
+ continue;
2390
+ }
2375
2391
  break;
2376
2392
  }
2377
2393
  const lyricLines = [rest];
@@ -2388,6 +2404,8 @@ var parseLyrics = (mml) => {
2388
2404
  octave,
2389
2405
  vibrato,
2390
2406
  reverb,
2407
+ gender,
2408
+ breathiness,
2391
2409
  syllables,
2392
2410
  metaText: metaTokens.join(" "),
2393
2411
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2483,12 +2501,13 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2483
2501
  const attack = 0.02;
2484
2502
  const release = 0.06;
2485
2503
  const sustainEnd = t0 + Math.max(attack + 0.02, e.duration);
2504
+ const dest = e.destination ?? destination;
2486
2505
  let panner = null;
2487
- let out = destination;
2506
+ let out = dest;
2488
2507
  if (typeof ctx.createStereoPanner === "function") {
2489
2508
  panner = ctx.createStereoPanner();
2490
2509
  panner.pan.value = Math.max(-1, Math.min(1, e.pan ?? 0));
2491
- panner.connect(destination);
2510
+ panner.connect(dest);
2492
2511
  out = panner;
2493
2512
  }
2494
2513
  let sendGain = null;
@@ -2716,7 +2735,7 @@ var createLocalBackend = async (options) => {
2716
2735
  }
2717
2736
  return p;
2718
2737
  };
2719
- const renderAlias = async (alias, pitch, durationMs, vibrato) => {
2738
+ const renderAlias = async (alias, pitch, durationMs, vibrato, expr) => {
2720
2739
  const pcm = await getPcm(alias);
2721
2740
  if (!pcm || pcm.length === 0) return null;
2722
2741
  const entry = bank.manifest.phonemes[alias];
@@ -2727,7 +2746,9 @@ var createLocalBackend = async (options) => {
2727
2746
  pcm,
2728
2747
  pitch: vibrato ? vibratoPitchCurve(targetHz, lead.preMs) : targetHz,
2729
2748
  durationMs,
2730
- ...lead
2749
+ ...lead,
2750
+ gender: expr?.gender,
2751
+ breathiness: expr?.breathiness
2731
2752
  });
2732
2753
  if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
2733
2754
  }
@@ -2792,7 +2813,7 @@ var createWorkerBackend = async (workerUrl, options) => {
2792
2813
  });
2793
2814
  onReady = null;
2794
2815
  onFail = null;
2795
- const renderAlias = (alias, pitch, durationMs, vibrato) => new Promise((resolve) => {
2816
+ const renderAlias = (alias, pitch, durationMs, vibrato, expr) => new Promise((resolve) => {
2796
2817
  const id = ++reqId;
2797
2818
  pending.set(
2798
2819
  id,
@@ -2806,7 +2827,9 @@ var createWorkerBackend = async (workerUrl, options) => {
2806
2827
  alias,
2807
2828
  pitch,
2808
2829
  durationMs,
2809
- vibrato
2830
+ vibrato,
2831
+ gender: expr?.gender,
2832
+ breathiness: expr?.breathiness
2810
2833
  });
2811
2834
  });
2812
2835
  return {
@@ -2835,15 +2858,21 @@ var createKoeVoice = async (ctx, destination, options) => {
2835
2858
  const inflight = /* @__PURE__ */ new Map();
2836
2859
  const active = /* @__PURE__ */ new Set();
2837
2860
  let prevVowel = "";
2838
- const keyOf = (alias, pitch, durationMs, vibrato) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}${vibrato ? "|vib" : ""}`;
2839
- const renderInto = (alias, pitch, durationMs, vibrato) => {
2840
- const key = keyOf(alias, pitch, durationMs, vibrato);
2861
+ const keyOf = (alias, pitch, durationMs, vibrato, expr) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}${vibrato ? "|vib" : ""}${expr?.gender !== void 0 ? `|g${Math.round(expr.gender * 100)}` : ""}${expr?.breathiness !== void 0 ? `|h${Math.round(expr.breathiness * 100)}` : ""}`;
2862
+ const renderInto = (alias, pitch, durationMs, vibrato, expr) => {
2863
+ const key = keyOf(alias, pitch, durationMs, vibrato, expr);
2841
2864
  const existing = renderCache.get(key);
2842
2865
  if (existing !== void 0) return Promise.resolve(existing);
2843
2866
  const flying = inflight.get(key);
2844
2867
  if (flying) return flying;
2845
2868
  const p = (async () => {
2846
- const out = await backend.renderAlias(alias, pitch, durationMs, vibrato);
2869
+ const out = await backend.renderAlias(
2870
+ alias,
2871
+ pitch,
2872
+ durationMs,
2873
+ vibrato,
2874
+ expr
2875
+ );
2847
2876
  let rendered = null;
2848
2877
  if (out) {
2849
2878
  const buf = ctx.createBuffer(1, out.pcm.length, KOE_SAMPLE_RATE);
@@ -2858,13 +2887,14 @@ var createKoeVoice = async (ctx, destination, options) => {
2858
2887
  return p;
2859
2888
  };
2860
2889
  const LEADCAP_S = 0.09;
2861
- const schedule = (r, t0, peak, pan, reverbSend = 0) => {
2862
- let out = destination;
2890
+ const schedule = (r, t0, peak, pan, reverbSend = 0, destOverride) => {
2891
+ const dest = destOverride ?? destination;
2892
+ let out = dest;
2863
2893
  let panner = null;
2864
2894
  if (typeof ctx.createStereoPanner === "function") {
2865
2895
  panner = ctx.createStereoPanner();
2866
2896
  panner.pan.value = Math.max(-1, Math.min(1, pan));
2867
- panner.connect(destination);
2897
+ panner.connect(dest);
2868
2898
  out = panner;
2869
2899
  }
2870
2900
  let sendGain = null;
@@ -2917,10 +2947,10 @@ var createKoeVoice = async (ctx, destination, options) => {
2917
2947
  const pan = e.pan ?? 0;
2918
2948
  const durationMs = Math.max(60, e.duration * 1e3);
2919
2949
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2920
- if (r) schedule(r, t0, peak, pan, e.reverbSend);
2950
+ if (r) schedule(r, t0, peak, pan, e.reverbSend, e.destination);
2921
2951
  });
2922
2952
  };
2923
- model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato) => {
2953
+ model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato, expr) => {
2924
2954
  if (syllable.consonant === "Q" || syllable.vowel === "") return null;
2925
2955
  const alias = resolveKoeAlias(
2926
2956
  backend.hasAlias,
@@ -2932,12 +2962,12 @@ var createKoeVoice = async (ctx, destination, options) => {
2932
2962
  if (!alias) return null;
2933
2963
  const dMs = Math.max(60, durationMs);
2934
2964
  const vib = !!vibrato && dMs / 1e3 >= VIBRATO_MIN_SEC;
2935
- const r = await renderInto(alias, pitch, dMs, vib);
2936
- return r ? keyOf(alias, pitch, dMs, vib) : null;
2965
+ const r = await renderInto(alias, pitch, dMs, vib, expr);
2966
+ return r ? keyOf(alias, pitch, dMs, vib, expr) : null;
2937
2967
  };
2938
- model.scheduleCached = (key, t0, peak, pan, reverbSend) => {
2968
+ model.scheduleCached = (key, t0, peak, pan, reverbSend, dest) => {
2939
2969
  const r = renderCache.get(key);
2940
- if (r) schedule(r, t0, peak, pan, reverbSend);
2970
+ if (r) schedule(r, t0, peak, pan, reverbSend, dest);
2941
2971
  };
2942
2972
  model.stopAll = () => {
2943
2973
  for (const src of active) {
@@ -3031,10 +3061,14 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3031
3061
  const m = loaded.get(track.model.toLowerCase());
3032
3062
  if (!m?.renderToCache) continue;
3033
3063
  let n = 0;
3064
+ const expr = {
3065
+ gender: track.gender,
3066
+ breathiness: track.breathiness
3067
+ };
3034
3068
  forEachSungNote(track, (note, prevVowel) => {
3035
3069
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
3036
3070
  n++;
3037
- tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato });
3071
+ tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
3038
3072
  });
3039
3073
  }
3040
3074
  const total = tasks.length;
@@ -3050,7 +3084,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3050
3084
  task.prevVowel,
3051
3085
  task.note.pitch,
3052
3086
  task.note.durationSec * 1e3,
3053
- task.vibrato
3087
+ task.vibrato,
3088
+ task.expr
3054
3089
  ) ?? Promise.resolve(null));
3055
3090
  done++;
3056
3091
  onProgress?.(done, total);
@@ -3096,13 +3131,21 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3096
3131
  prevVowel,
3097
3132
  note.pitch,
3098
3133
  note.durationSec * 1e3,
3099
- track.vibrato
3134
+ track.vibrato,
3135
+ { gender: track.gender, breathiness: track.breathiness }
3100
3136
  );
3101
3137
  if (session !== streamSession) return;
3102
3138
  if (key) {
3103
3139
  const delay = ctx.currentTime - t0;
3104
3140
  if (delay < 0.05) {
3105
- scheduleCached(key, t0, peak, track.pan, track.reverbSend);
3141
+ scheduleCached(
3142
+ key,
3143
+ t0,
3144
+ peak,
3145
+ track.pan,
3146
+ track.reverbSend,
3147
+ options.getTrackDestination?.(track.id ?? "")
3148
+ );
3106
3149
  opts?.onScheduled?.(track, note, t0);
3107
3150
  } else {
3108
3151
  console.warn(
@@ -3115,14 +3158,15 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3115
3158
  } else {
3116
3159
  const when = t0 - ctx.currentTime;
3117
3160
  model(note.syllable, {
3118
- trackId: "",
3161
+ trackId: track.id ?? "",
3119
3162
  pitch: note.pitch,
3120
3163
  velocity: 100,
3121
3164
  volume: peak,
3122
3165
  when,
3123
3166
  duration: note.durationSec,
3124
3167
  pan: track.pan,
3125
- reverbSend: track.reverbSend
3168
+ reverbSend: track.reverbSend,
3169
+ destination: options.getTrackDestination?.(track.id ?? "")
3126
3170
  });
3127
3171
  opts?.onScheduled?.(track, note, t0);
3128
3172
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -3181,8 +3225,10 @@ var PITCH_MAP = {
3181
3225
  b: 11
3182
3226
  };
3183
3227
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3184
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|mode)=([\w-]+)/gi;
3228
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|mode)=([\w-]+)/gi;
3185
3229
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3230
+ var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3231
+ var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3186
3232
  var parseMmlMeta = (mml) => {
3187
3233
  const meta = {};
3188
3234
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -3196,6 +3242,9 @@ var parseMmlMeta = (mml) => {
3196
3242
  } else if (key === "drumvolume") {
3197
3243
  const dv = Number.parseInt(m[2], 10);
3198
3244
  if (!Number.isNaN(dv)) meta.drumVolume = dv;
3245
+ } else if (key === "reverb") {
3246
+ const rv = Number.parseInt(m[2], 10);
3247
+ if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3199
3248
  } else if (key === "mode") {
3200
3249
  if (m[2] === "simple" || m[2] === "advanced") {
3201
3250
  meta.mode = m[2];
@@ -3210,9 +3259,25 @@ var parseMmlMeta = (mml) => {
3210
3259
  meta.trackInstruments[idx] = name;
3211
3260
  }
3212
3261
  }
3262
+ for (const m of mml.matchAll(TRACK_COMP_DIRECTIVE)) {
3263
+ const idx = Number.parseInt(m[1], 10);
3264
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3265
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3266
+ meta.trackCompression ??= {};
3267
+ meta.trackCompression[idx] = val;
3268
+ }
3269
+ }
3270
+ for (const m of mml.matchAll(TRACK_WIDTH_DIRECTIVE)) {
3271
+ const idx = Number.parseInt(m[1], 10);
3272
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 200);
3273
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3274
+ meta.trackWidth ??= {};
3275
+ meta.trackWidth[idx] = val;
3276
+ }
3277
+ }
3213
3278
  return meta;
3214
3279
  };
3215
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "");
3280
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "");
3216
3281
  var formatMmlMeta = (meta, space = "") => {
3217
3282
  const parts = [];
3218
3283
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3221,12 +3286,24 @@ var formatMmlMeta = (meta, space = "") => {
3221
3286
  if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
3222
3287
  if (meta.drumVolume !== void 0)
3223
3288
  parts.push(`#drumvolume=${meta.drumVolume}`);
3289
+ if (meta.reverb !== void 0 && meta.reverb !== 0)
3290
+ parts.push(`#reverb=${meta.reverb}`);
3224
3291
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3225
3292
  if (meta.trackInstruments) {
3226
3293
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
3227
3294
  if (name) parts.push(`#t${idx}inst=${name}`);
3228
3295
  }
3229
3296
  }
3297
+ if (meta.trackCompression) {
3298
+ for (const [idx, val] of Object.entries(meta.trackCompression)) {
3299
+ if (val !== 0) parts.push(`#t${idx}comp=${val}`);
3300
+ }
3301
+ }
3302
+ if (meta.trackWidth) {
3303
+ for (const [idx, val] of Object.entries(meta.trackWidth)) {
3304
+ if (val !== 100) parts.push(`#t${idx}width=${val}`);
3305
+ }
3306
+ }
3230
3307
  return parts.join(space);
3231
3308
  };
3232
3309
  var parseMML = (mml, options = {}) => {
@@ -6968,6 +7045,23 @@ var DAW_CSS = `
6968
7045
  @keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
6969
7046
  .dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
6970
7047
 
7048
+ /* \u2500\u2500\u2500 \u97F3\u5272\u308C\u691C\u77E5\u30D0\u30C3\u30B8 \u2500\u2500\u2500 \u30AF\u30EA\u30C3\u30D7\u767A\u751F\u4E2D\u3060\u3051\u8868\u793A\u3055\u308C\u308B\u8B66\u544A\u30DC\u30BF\u30F3\u3002\u30AF\u30EA\u30C3\u30AF\u3067\u6D88\u305B\u308B\u3002 */
7049
+ .dtm-clip-badge {
7050
+ flex: 0 0 auto;
7051
+ min-height: 24px;
7052
+ padding: 0 8px;
7053
+ border: 2px solid var(--c-black);
7054
+ background: var(--dtm-danger);
7055
+ color: var(--c-white);
7056
+ font-family: var(--dtm-font);
7057
+ font-size: 10px;
7058
+ letter-spacing: .1em;
7059
+ cursor: pointer;
7060
+ box-shadow: 0 0 0 2px var(--dtm-danger), 2px 2px 0 var(--c-black);
7061
+ animation: dtm-blink .5s steps(1) infinite;
7062
+ }
7063
+ .dtm-clip-badge:active { transform: translate(2px,2px); box-shadow: none; }
7064
+
6971
7065
  /* \u2500\u2500\u2500 \u30A4\u30F3\u30D5\u30A9\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
6972
7066
  .dtm-infobtn {
6973
7067
  display: inline-flex;
@@ -7243,6 +7337,37 @@ var DAW_CSS = `
7243
7337
  object-fit: cover;
7244
7338
  image-rendering: pixelated;
7245
7339
  }
7340
+ /* \u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u8A73\u7D30\u8A2D\u5B9A\u300D\u2014 \u5E38\u7528\u3057\u306A\u3044\u30D1\u30E9\u30E1\u30FC\u30BF\uFF08\u30AA\u30AF\u30BF\u30FC\u30D6/\u5B9A\u4F4D/\u30EA\u30D0\u30FC\u30D6\u9001\u308A/
7341
+ \u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9/\u30D3\u30D6\u30E9\u30FC\u30C8\uFF09\u3092\u7573\u3093\u3067\u304A\u304F\u8EFD\u91CF\u306A details\u3002dtm-panel \u307B\u3069
7342
+ \u4EF0\u3005\u3057\u304F\u305B\u305A\u3001\u63A7\u3048\u3081\u306A\u4ED5\u5207\u308A\u7DDA\uFF0B\u5C0F\u3055\u3044\u25B6\u30DE\u30FC\u30AB\u30FC\u3060\u3051\u4ED8\u3051\u308B\u3002 */
7343
+ .dtm-advanced {
7344
+ border-top: 1px dashed var(--dtm-border2);
7345
+ padding-top: 6px;
7346
+ display: flex;
7347
+ flex-direction: column;
7348
+ gap: 6px;
7349
+ }
7350
+ .dtm-advanced > summary {
7351
+ list-style: none;
7352
+ cursor: pointer;
7353
+ font-size: 11px;
7354
+ letter-spacing: .08em;
7355
+ color: var(--dtm-text);
7356
+ display: flex;
7357
+ align-items: center;
7358
+ gap: 6px;
7359
+ min-height: 28px;
7360
+ padding: 0 8px;
7361
+ border: 2px solid var(--dtm-border2);
7362
+ background: var(--dtm-deep);
7363
+ box-shadow: 2px 2px 0 var(--c-black);
7364
+ width: fit-content;
7365
+ }
7366
+ .dtm-advanced > summary:active { transform: translate(2px,2px); box-shadow: none; }
7367
+ .dtm-advanced > summary::-webkit-details-marker { display: none; }
7368
+ .dtm-advanced > summary::before { content: "\u25B6"; font-size: 9px; color: var(--dtm-accent); }
7369
+ .dtm-advanced[open] > summary::before { content: "\u25BC"; }
7370
+ .dtm-advanced[open] > summary { border-color: var(--dtm-primary); color: var(--dtm-primary); }
7246
7371
 
7247
7372
  /* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */
7248
7373
  @media (min-width: 768px) {
@@ -9014,6 +9139,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9014
9139
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
9015
9140
  vibrato: lt.vibrato,
9016
9141
  reverbSend: (lt.reverb ?? 0) / 100,
9142
+ gender: (lt.gender ?? 50) / 100,
9143
+ breathiness: (lt.breathiness ?? 50) / 100,
9017
9144
  notes
9018
9145
  };
9019
9146
  });
@@ -10715,6 +10842,7 @@ var buildUI = (target, options) => {
10715
10842
  <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
10716
10843
  <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
10717
10844
  <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
10845
+ <button class="dtm-clip-badge dtm-hidden" data-dtm="clip-badge" title="\u97F3\u5272\u308C\u691C\u77E5\uFF08\u30AF\u30EA\u30C3\u30AF\u3067\u6D88\u3059\uFF09">CLIP</button>
10718
10846
  <span class="dtm-grow"></span>
10719
10847
  <span class="dtm-label">BPM</span>
10720
10848
  <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
@@ -10752,18 +10880,8 @@ var buildUI = (target, options) => {
10752
10880
  <div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
10753
10881
 
10754
10882
  <details class="dtm-panel" open>
10755
- <summary>\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10883
+ <summary>\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10756
10884
  <div class="dtm-panel-body">
10757
- <div class="dtm-row">
10758
- <span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
10759
- <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10760
- <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10761
- </div>
10762
- <div class="dtm-row">
10763
- <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6</span>
10764
- <input type="range" class="dtm-range dtm-grow" data-dtm="reverb-amount" value="0" min="0" max="100" aria-label="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\uFF08\u5168\u30C8\u30E9\u30C3\u30AF\u3078\u4E00\u5F8B\u306B\u639B\u304B\u308B\u6B8B\u97FF\uFF09" title="\u30EA\u30D0\u30FC\u30D6">
10765
- <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10766
- </div>
10767
10885
  <div class="dtm-track-body" data-dtm="track-body"></div>
10768
10886
  </div>
10769
10887
  </details>
@@ -10796,6 +10914,29 @@ var buildUI = (target, options) => {
10796
10914
  </div>
10797
10915
  </details>
10798
10916
 
10917
+ <details class="dtm-panel" open>
10918
+ <summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10919
+ <div class="dtm-panel-body">
10920
+ <div data-dtm="preset-select-slot"></div>
10921
+ <div class="dtm-row">
10922
+ <span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
10923
+ <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10924
+ <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10925
+ </div>
10926
+ <div class="dtm-row">
10927
+ <button class="dtm-btn dtm-btn--ghost dtm-btn--xs" data-dtm="auto-master" title="\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\u30FB\u30EA\u30D0\u30FC\u30D6\u3092\u5546\u696D\u66F2\u5BC4\u308A\u306E\u5024\u306B\u4E00\u62EC\u8A2D\u5B9A\u3057\u307E\u3059">\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0</button>
10928
+ <button class="dtm-infobtn" data-dtm="auto-master-info" title="\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10929
+ <span class="dtm-grow"></span>
10930
+ </div>
10931
+ <div class="dtm-row">
10932
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6</span>
10933
+ <input type="range" class="dtm-range dtm-grow" data-dtm="reverb-amount" value="0" min="0" max="100" aria-label="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\uFF08\u5168\u30C8\u30E9\u30C3\u30AF\u3078\u4E00\u5F8B\u306B\u639B\u304B\u308B\u6B8B\u97FF\uFF09">
10934
+ <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10935
+ <button class="dtm-infobtn" data-dtm="reverb-amount-info" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10936
+ </div>
10937
+ </div>
10938
+ </details>
10939
+
10799
10940
  <details class="dtm-panel">
10800
10941
  <summary>\u30C9\u30E9\u30E0\u8A2D\u5B9A</summary>
10801
10942
  <div class="dtm-panel-body">
@@ -10961,6 +11102,7 @@ var buildUI = (target, options) => {
10961
11102
  prevBarBtn: sel("prev-bar"),
10962
11103
  nextBarBtn: sel("next-bar"),
10963
11104
  soloCheckbox: sel("solo"),
11105
+ clipBadge: sel("clip-badge"),
10964
11106
  toolPen: sel("tool-pen"),
10965
11107
  toolSelect: sel("tool-select"),
10966
11108
  toolEraser: sel("tool-eraser"),
@@ -10989,6 +11131,9 @@ var buildUI = (target, options) => {
10989
11131
  masterVolumeLabel: sel("master-volume-label"),
10990
11132
  reverbAmount: sel("reverb-amount"),
10991
11133
  reverbAmountLabel: sel("reverb-amount-label"),
11134
+ reverbAmountInfoBtn: sel("reverb-amount-info"),
11135
+ autoMasterBtn: sel("auto-master"),
11136
+ autoMasterInfoBtn: sel("auto-master-info"),
10992
11137
  trackTabs: sel("track-tabs"),
10993
11138
  trackBody: sel("track-body"),
10994
11139
  drumSelect: sel("drum-select"),
@@ -12653,10 +12798,85 @@ var CHORD_INFO_HTML2 = `
12653
12798
  `;
12654
12799
  var VIBRATO_INFO_HTML = `
12655
12800
  <div class="dtm-modal-body-content">
12656
- <h4>\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u306F</h4>
12801
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12657
12802
  <p>ON\u306B\u3059\u308B\u3068\u3001\u4E00\u5B9A\u306E\u9577\u3055\uFF08\u7D040.35\u79D2\uFF09\u4EE5\u4E0A\u306E\u97F3\u7B26\uFF08\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\uFF09\u306B\u3060\u3051\u3001\u81EA\u52D5\u3067\u30D4\u30C3\u30C1\u304C\u5C0F\u523B\u307F\u306B\u63FA\u308C\u308B\u6B4C\u5531\u8868\u73FE\uFF08\u30D3\u30D6\u30E9\u30FC\u30C8\uFF09\u304C\u639B\u304B\u308A\u307E\u3059\u3002</p>
12658
- <p style="margin-top:4px;"><small>\u77ED\u3044\u97F3\u7B26\u306B\u306F\u639B\u304B\u308A\u307E\u305B\u3093\u30021\u5468\u671F\u3082\u63FA\u308C\u304D\u3089\u306A\u3044\u3046\u3061\u306B\u6B21\u306E\u97F3\u3078\u79FB\u3063\u3066\u3057\u307E\u3044\u3001\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u3044\u3046\u3088\u308A\u5358\u306A\u308B\u97F3\u7A0B\u306E\u30D6\u30EC\u3068\u3057\u3066\u4E0D\u81EA\u7136\u306B\u805E\u3053\u3048\u308B\u305F\u3081\u3067\u3059\u3002</small></p>
12659
- <p style="margin-top:4px;"><small>\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\u3057\u3066\u3044\u307E\u3059\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</small></p>
12803
+ <h4>\u77ED\u3044\u97F3\u7B26\u306B\u639B\u304B\u3089\u306A\u3044\u7406\u7531</h4>
12804
+ <p>1\u5468\u671F\u3082\u63FA\u308C\u304D\u3089\u306A\u3044\u3046\u3061\u306B\u6B21\u306E\u97F3\u3078\u79FB\u3063\u3066\u3057\u307E\u3044\u3001\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u3044\u3046\u3088\u308A\u5358\u306A\u308B\u97F3\u7A0B\u306E\u30D6\u30EC\u3068\u3057\u3066\u4E0D\u81EA\u7136\u306B\u805E\u3053\u3048\u308B\u305F\u3081\u3067\u3059\u3002</p>
12805
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12806
+ <p>\u30D4\u30C3\u30C1\u3092\u63FA\u3089\u3059\u52B9\u679C\u306A\u306E\u3067\u3001\u58F0\u8CEA\u305D\u306E\u3082\u306E\u3092\u5909\u3048\u308B\u30B8\u30A7\u30F3\u30C0\u30FC/\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306F\u72EC\u7ACB\u3057\u3066\u7D44\u307F\u5408\u308F\u305B\u3089\u308C\u307E\u3059\u3002\u901F\u3055\u30FB\u6DF1\u3055\u306F\u8ABF\u6574\u3067\u304D\u307E\u305B\u3093\uFF08\u6B4C\u3068\u3057\u3066\u7834\u7DBB\u3057\u306B\u304F\u3044\u63A7\u3048\u3081\u306A\u91CF\u306B\u56FA\u5B9A\uFF09\u3002\u66F2\u3084\u7B87\u6240\u3054\u3068\u306B\u639B\u3051\u305F\u3044/\u639B\u3051\u305F\u304F\u306A\u3044\u304C\u3042\u308B\u5834\u5408\u306F\u3001\u30C8\u30E9\u30C3\u30AF\u3092\u5206\u3051\u3066\u6B4C\u8A5E\u3092\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002</p>
12807
+ </div>
12808
+ `;
12809
+ var GENDER_INFO_HTML = `
12810
+ <div class="dtm-modal-body-content">
12811
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12812
+ <p>\u30D4\u30C3\u30C1\uFF08\u97F3\u306E\u9AD8\u3055\uFF09\u306F\u305D\u306E\u307E\u307E\u306B\u3001\u58F0\u306E\u592A\u3055/\u7D30\u3055\uFF08\u30D5\u30A9\u30EB\u30DE\u30F3\u30C8\uFF1D\u58F0\u9053\u306E\u5171\u9CF4\u3001\u5E74\u9F62\u30FB\u6027\u5225\u611F\u306E\u5370\u8C61\uFF09\u3060\u3051\u3092\u52D5\u304B\u3057\u307E\u3059\u300250\u304C\u7121\u5909\u5316\u300150\u672A\u6E80\u3067\u4F4E\u3081/\u592A\u3081\uFF08\u5927\u4EBA\u3073\u308B\uFF09\u300150\u8D85\u3067\u9AD8\u3081/\u7D30\u3081\uFF08\u82E5\u304F/\u660E\u308B\u304F\uFF09\u306B\u5BC4\u308A\u307E\u3059\u3002</p>
12813
+ <h4>\u30AA\u30AF\u30BF\u30FC\u30D6\u30B7\u30D5\u30C8\u3068\u306E\u9055\u3044</h4>
12814
+ <p>\u30AA\u30AF\u30BF\u30FC\u30D6\u306F\u97F3\u7A0B\u305D\u306E\u3082\u306E\u3092\u4E0A\u4E0B\u3055\u305B\u307E\u3059\u304C\u3001\u30B8\u30A7\u30F3\u30C0\u30FC\u306F\u97F3\u7A0B\u3092\u5909\u3048\u305A\u306B\u58F0\u8272\u3060\u3051\u3092\u52D5\u304B\u3057\u307E\u3059\u3002\u300C\u9AD8\u3044\u58F0\u306E\u307E\u307E\u5927\u4EBA\u3073\u3055\u305B\u308B\u300D\u300C\u4F4E\u3044\u58F0\u306E\u307E\u307E\u82E5\u3005\u3057\u304F\u3059\u308B\u300D\u3068\u3044\u3063\u305F\u3001\u97F3\u7A0B\u3068\u58F0\u8CEA\u3092\u5225\u3005\u306B\u8ABF\u6574\u3057\u305F\u3044\u3068\u304D\u306B\u4F7F\u3044\u307E\u3059\u3002</p>
12815
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12816
+ <p>\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u7D44\u307F\u5408\u308F\u305B\u3066\u58F0\u306E\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u3092\u4F5C\u308A\u307E\u3059\uFF08\u4F8B: \u4F4E\u3081+\u606F\u591A\u3081\u3067\u6E0B\u3044/\u5927\u4EBA\u3063\u307D\u3044\u5370\u8C61\u3001\u9AD8\u3081+\u606F\u5C11\u306A\u3081\u3067\u5143\u6C17/\u82E5\u3005\u3057\u3044\u5370\u8C61\uFF09\u3002koe\u97F3\u6E90\uFF08UTAU\u7531\u6765\u306E.koe\u97F3\u6E90\uFF09\u9650\u5B9A\u306E\u52B9\u679C\u3067\u3001klatt\uFF08\u5185\u8535\u306E\u7C21\u6613\u5408\u6210\uFF09\u3067\u306F\u5909\u5316\u3057\u307E\u305B\u3093\u3002</p>
12817
+ </div>
12818
+ `;
12819
+ var BREATHINESS_INFO_HTML = `
12820
+ <div class="dtm-modal-body-content">
12821
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12822
+ <p>\u606F\u6210\u5206\u306E\u91CF\u3067\u3059\u300250\u304C\u7121\u5909\u5316\u3001\u5927\u304D\u3044\u307B\u3069\u606F\u3063\u307D\u304F\uFF08\u3055\u3055\u3084\u304D\u5BC4\u308A\uFF09\u3001\u5C0F\u3055\u3044\u307B\u3069\u82AF\u306E\u3042\u308B\u58F0\u306B\u306A\u308A\u307E\u3059\u3002</p>
12823
+ <h4>\u4E0A\u3052\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12824
+ <p>\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\u3067\u97F3\u7A0B\u611F\u304C\u8584\u308C\u3066\u805E\u3053\u3048\u307E\u3059\uFF08\u606F\u306E\u97F3\u304C\u30D4\u30C3\u30C1\u611F\u3092\u96A0\u3059\u305F\u3081\uFF09\u3002\u56C1\u304F\u3088\u3046\u306A\u30D0\u30E9\u30FC\u30C9\u8868\u73FE\u306B\u306F\u52B9\u679C\u7684\u3067\u3059\u304C\u3001\u4E0A\u3052\u3059\u304E\u308B\u3068\u30E1\u30ED\u30C7\u30A3\u304C\u4F1D\u308F\u308A\u306B\u304F\u304F\u306A\u308A\u307E\u3059\u3002</p>
12825
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12826
+ <p>\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u7D44\u307F\u5408\u308F\u305B\u308B\u3068\u3001\u63FA\u308C\u306A\u304C\u3089\u606F\u3063\u307D\u3044\u3001\u3088\u308A\u30A8\u30E2\u30FC\u30B7\u30E7\u30CA\u30EB\u306A\u8868\u73FE\u306B\u306A\u308A\u3084\u3059\u3044\u3067\u3059\u3002koe\u97F3\u6E90\uFF08UTAU\u7531\u6765\u306E.koe\u97F3\u6E90\uFF09\u9650\u5B9A\u306E\u52B9\u679C\u3067\u3001klatt\uFF08\u5185\u8535\u306E\u7C21\u6613\u5408\u6210\uFF09\u3067\u306F\u5909\u5316\u3057\u307E\u305B\u3093\u3002</p>
12827
+ </div>
12828
+ `;
12829
+ var LYRIC_REVERB_INFO_HTML = `
12830
+ <div class="dtm-modal-body-content">
12831
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12832
+ <p>\u3053\u306E\u30DC\u30FC\u30AB\u30EB\u30C8\u30E9\u30C3\u30AF\u304B\u3089\u3001\u66F2\u5168\u4F53\u306B\u639B\u304B\u308B\u300C\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u300D\u3078\u3069\u308C\u3060\u3051\u97F3\u3092\u9001\u308B\u304B\u3092\u6C7A\u3081\u307E\u3059\uFF080\u3067\u9001\u3089\u306A\u3044\uFF1D\u30C9\u30E9\u30A4\u3001100\u3067\u76EE\u4E00\u676F\u9001\u308B\uFF09\u3002</p>
12833
+ <h4>\u30DE\u30B9\u30BF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u300D\u3064\u307E\u307F\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12834
+ <p>\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A\u30D1\u30CD\u30EB\u306B\u3042\u308B\u30DE\u30B9\u30BF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u300D\u3064\u307E\u307F\u304C0%\u3060\u3068\u3001\u3053\u3053\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u300C\u9001\u308A\u91CF\uFF08\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304C\u3069\u308C\u3060\u3051\u63D0\u4F9B\u3059\u308B\u304B\uFF09\u300D\u3068\u300C\u30DE\u30B9\u30BF\u306E\u6B8B\u97FF\u8A2D\u5B9A\uFF08\u5B9F\u969B\u306B\u3069\u3093\u306A\u97FF\u304D\u304C\u639B\u304B\u308B\u304B\uFF09\u300D\u306E\u4E8C\u6BB5\u69CB\u3048\u306B\u306A\u3063\u3066\u3044\u308B\u305F\u3081\u3067\u3059\u3002\u4E21\u65B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
12835
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12836
+ <p>\u6B8B\u97FF\u3067\u97F3\u304C\u6EF2\u307F\u3001\u6B4C\u8A5E\u304C\u805E\u304D\u53D6\u308A\u306B\u304F\u304F\u306A\u308A\u307E\u3059\u3002\u30C9\u30E9\u30A4\u306A\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u8FD1\u3044\u300D\u300C\u524D\u306B\u51FA\u308B\u300D\u5370\u8C61\u3001\u30EA\u30D0\u30FC\u30D6\u305F\u3063\u3077\u308A\u306E\u30DC\u30FC\u30AB\u30EB\u306F\u300C\u5965\u884C\u304D\u304C\u3042\u308B\u300D\u300C\u5E7B\u60F3\u7684\u300D\u306A\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u3002\u8868\u73FE\u3057\u305F\u3044\u8DDD\u96E2\u611F\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\u3002</p>
12837
+ </div>
12838
+ `;
12839
+ var MASTER_REVERB_INFO_HTML = `
12840
+ <div class="dtm-modal-body-content">
12841
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12842
+ <p>\u66F2\u5168\u4F53\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308B\u6B8B\u97FF\uFF08\u90E8\u5C4B\u9CF4\u308A\u30FB\u7A7A\u9593\u306E\u97FF\u304D\uFF09\u3067\u3059\u30020%\u3067\u5B8C\u5168\u306B\u30C9\u30E9\u30A4\uFF08\u97FF\u304D\u306A\u3057\uFF09\u3001\u4E0A\u3052\u308B\u307B\u3069\u5E83\u3044\u7A7A\u9593\u3067\u9CF4\u3063\u3066\u3044\u308B\u3088\u3046\u306A\u5965\u884C\u304D\u304C\u51FA\u307E\u3059\u3002</p>
12843
+ <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12844
+ <p>\u6B4C\u8A5E\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u305D\u308C\u305E\u308C\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u3044\u3046\u500B\u5225\u306E\u3064\u307E\u307F\u304C\u3042\u308A\u3001\u305D\u3061\u3089\u304C0%\u306E\u30C8\u30E9\u30C3\u30AF\u306F\u3053\u306E\u30DE\u30B9\u30BF\u306E\u5024\u3092\u3044\u304F\u3089\u4E0A\u3052\u3066\u3082\u7121\u97F3\u306E\u307E\u307E\u3067\u3059\u3002\u9006\u306B\u3053\u306E\u30DE\u30B9\u30BF\u304C0%\u306A\u3089\u3001\u3069\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u9001\u308A\u91CF\u3092\u4E0A\u3052\u3066\u3082\u52B9\u679C\u304C\u51FA\u307E\u305B\u3093\u3002\u300C\u30DE\u30B9\u30BF\uFF1D\u6B8B\u97FF\u306E\u8CEA\u3068\u91CF\u305D\u306E\u3082\u306E\u300D\u300C\u30C8\u30E9\u30C3\u30AF\u9001\u308A\uFF1D\u305D\u306E\u30C8\u30E9\u30C3\u30AF\u3092\u3069\u308C\u3060\u3051\u6DF7\u305C\u308B\u304B\u300D\u3068\u3044\u3046\u4E8C\u6BB5\u69CB\u3048\u3067\u3059\u3002\u697D\u5668\u30C8\u30E9\u30C3\u30AF\u306B\u306F\u73FE\u72B6\u30BB\u30F3\u30C9\u6A5F\u80FD\u304C\u7121\u3044\u305F\u3081\u3001\u5E38\u306B\u4E00\u5F8B\u3067\u639B\u304B\u308A\u307E\u3059\u3002</p>
12845
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12846
+ <p>\u30DF\u30C3\u30AF\u30B9\u5168\u4F53\u306E\u8F2A\u90ED\u304C\u307C\u3084\u3051\u3001\u9060\u304F\u30FB\u3053\u3082\u3063\u305F\u5370\u8C61\u306B\u306A\u308A\u307E\u3059\u300210\u301C30%\u7A0B\u5EA6\u3092\u51FA\u767A\u70B9\u306B\u3001\u66F2\u306E\u30B8\u30E3\u30F3\u30EB\u3084\u7A7A\u9593\u306E\u5E83\u3055\u306E\u30A4\u30E1\u30FC\u30B8\u306B\u5408\u308F\u305B\u3066\u8ABF\u6574\u3059\u308B\u306E\u304C\u304A\u3059\u3059\u3081\u3067\u3059\u3002</p>
12847
+ </div>
12848
+ `;
12849
+ var TRACK_COMPRESSION_INFO_HTML = `
12850
+ <div class="dtm-modal-body-content">
12851
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12852
+ <p>\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306B\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u3092\u639B\u3051\u3001\u97F3\u91CF\u306E\u5927\u5C0F\u5DEE\u3092\u7E2E\u3081\u3066\u300C\u524D\u306B\u51FA\u308B\u300D\u300C\u805E\u3053\u3048\u3084\u3059\u3044\u300D\u97F3\u306B\u3057\u307E\u3059\u3002\u5E02\u8CA9\u66F2\u306E\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3084\u30DF\u30C3\u30AF\u30B9\u3067\u5B9A\u756A\u306E\u51E6\u7406\u3067\u3059\u30020\u3067\u7121\u5727\u7E2E\u3001100\u306B\u8FD1\u3065\u304F\u307B\u3069\u5F37\u304F\u5727\u7E2E\u3055\u308C\u307E\u3059\u3002</p>
12853
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12854
+ <p>\u5F37\u5F31\u306E\u8868\u60C5\uFF08\u30C0\u30A4\u30CA\u30DF\u30AF\u30B9\uFF09\u304C\u5931\u308F\u308C\u3066\u5358\u8ABF\u306B\u805E\u3053\u3048\u307E\u3059\u3002\u30DC\u30FC\u30AB\u30EB\u3084\u30EA\u30FC\u30C9\u697D\u5668\u306F\u63A7\u3048\u3081\uFF0820\u301C40\u7A0B\u5EA6\uFF09\u3001\u30C9\u30E9\u30E0\u3084\u30D9\u30FC\u30B9\u306F\u3084\u3084\u5F37\u3081\u3001\u304C\u4E00\u822C\u7684\u306A\u76EE\u5B89\u3067\u3059\u3002</p>
12855
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12856
+ <p>\u30DE\u30B9\u30BF\u306E\u300C\u5B89\u5168\u30EA\u30DF\u30C3\u30BF\u30FC\u300D\uFF08\u5E38\u6642ON\u3001[reverb]\u30D1\u30CD\u30EB\u306E\u5916\u5074\u3067\u81EA\u52D5\u7684\u306B\u50CD\u304F\u4FDD\u967A\uFF09\u3068\u306F\u5225\u7269\u3067\u3059\u3002\u3042\u3061\u3089\u306F\u97F3\u5272\u308C\u3092\u7269\u7406\u7684\u306B\u9632\u3050\u305F\u3081\u306E\u6700\u7D42\u9632\u885B\u30E9\u30A4\u30F3\u3067\u3001\u5E38\u306B\u63A7\u3048\u3081\u306B\u52D5\u3044\u3066\u3044\u307E\u3059\u3002\u3053\u3061\u3089\u306E\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u306F\u97F3\u4F5C\u308A\uFF08\u8868\u73FE\uFF09\u306E\u305F\u3081\u306E\u51E6\u7406\u3067\u3001\u30C8\u30E9\u30C3\u30AF\u3054\u3068\u306B\u597D\u304D\u306A\u3060\u3051\u5F37\u304F/\u5F31\u304F\u639B\u3051\u3089\u308C\u307E\u3059\u3002\u300C\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u300D\u306F\u5168\u30C8\u30E9\u30C3\u30AF\u4E00\u5F8B35%\u3092\u5F53\u3066\u308B\u3060\u3051\u306A\u306E\u3067\u3001\u30DC\u30FC\u30AB\u30EB\u306A\u3069\u76EE\u7ACB\u305F\u305B\u305F\u3044\u30D1\u30FC\u30C8\u306F\u5F8C\u3067\u500B\u5225\u306B\u4E0B\u3052\u308B\u3068\u8F2A\u90ED\u304C\u51FA\u3084\u3059\u304F\u306A\u308A\u307E\u3059\u3002</p>
12857
+ </div>
12858
+ `;
12859
+ var TRACK_WIDTH_INFO_HTML = `
12860
+ <div class="dtm-modal-body-content">
12861
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12862
+ <p>\u5DE6\u53F3\u306E\u5E83\u304C\u308A\u3092\u8ABF\u6574\u3057\u307E\u3059\u3002100\u304C\u539F\u97F3\u306E\u307E\u307E\u30010\u3067\u5B8C\u5168\u30E2\u30CE\u30E9\u30EB\uFF08\u5DE6\u53F3\u304C\u540C\u3058\u97F3\uFF09\u3001100\u3092\u8D85\u3048\u308B\u3068\u5DE6\u53F3\u306E\u9055\u3044\u304C\u8A87\u5F35\u3055\u308C\u3066\u5E83\u304F\u805E\u3053\u3048\u307E\u3059\u3002</p>
12863
+ <h4>\u300C\u5B9A\u4F4D\uFF08\u30D1\u30F3\uFF09\u300D\u3068\u306E\u9055\u3044</h4>
12864
+ <p>\u5B9A\u4F4D\u306F\u300C\u97F3\u3092\u3069\u3053\u306B\u7F6E\u304F\u304B\u300D\uFF08\u5DE6\u5BC4\u308A/\u4E2D\u592E/\u53F3\u5BC4\u308A\uFF09\u3001\u30B9\u30C6\u30EC\u30AA\u5E45\u306F\u300C\u305D\u306E\u97F3\u81EA\u4F53\u304C\u3069\u308C\u3060\u3051\u5E83\u304C\u3063\u3066\u805E\u3053\u3048\u308B\u304B\u300D\u3067\u3001\u5F79\u5272\u304C\u7570\u306A\u308A\u307E\u3059\u3002\u4E21\u65B9\u3092\u5F37\u304F\u4F7F\u3046\u3068\u5B9A\u4F4D\u304C\u307C\u3084\u3051\u3066\u66D6\u6627\u306B\u306A\u308A\u304C\u3061\u3067\u3059\u3002</p>
12865
+ <h4>\u5E83\u3052\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12866
+ <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>
12867
+ </div>
12868
+ `;
12869
+ var AUTO_MASTER_INFO_HTML = `
12870
+ <div class="dtm-modal-body-content">
12871
+ <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
12872
+ <p>\u5E02\u8CA9\u66F2\u3067\u3088\u304F\u4F7F\u308F\u308C\u308B\u5024\u3092\u76EE\u5B89\u306B\u3001\u4EE5\u4E0B\u3092\u307E\u3068\u3081\u3066\u8A2D\u5B9A\u3059\u308B\u30DC\u30BF\u30F3\u3067\u3059\u3002</p>
12873
+ <ul>
12874
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6: 20%</li>
12875
+ <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: 35%</li>
12876
+ <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45: 115%\uFF08\u308F\u305A\u304B\u306B\u5E83\u3052\u308B\uFF09</li>
12877
+ <li>\u6B4C\u8A5E\u306E\u3042\u308B\u30C8\u30E9\u30C3\u30AF: \u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8ON\u3001\u30EA\u30D0\u30FC\u30D6\u9001\u308A25%</li>
12878
+ </ul>
12879
+ <p style="margin-top:4px;"><small>\u300C\u3044\u3044\u611F\u3058\u306E\u521D\u671F\u5024\u300D\u3092\u4E00\u62EC\u3067\u5F53\u3066\u308B\u3060\u3051\u3067\u3001\u66F2\u3084\u597D\u307F\u306B\u5FDC\u3058\u305F\u5FAE\u8ABF\u6574\u307E\u3067\u306F\u884C\u3044\u307E\u305B\u3093\u3002\u65E2\u5B58\u306E\u8A2D\u5B9A\u306F\u4E0A\u66F8\u304D\u3055\u308C\u308B\u306E\u3067\u3001\u6C17\u306B\u5165\u3089\u306A\u3051\u308C\u3070\u5404\u30B9\u30E9\u30A4\u30C0\u30FC\u304B\u3089\u500B\u5225\u306B\u623B\u3057\u3066\u304F\u3060\u3055\u3044\u3002</small></p>
12660
12880
  </div>
12661
12881
  `;
12662
12882
  var MIDI_INFO_HTML = `
@@ -12948,6 +13168,7 @@ var mountDAW = (target, options = {}) => {
12948
13168
  let masterVolume = options.masterVolume ?? 50;
12949
13169
  options.singingVoices?.setVolume(masterVolume / 100);
12950
13170
  let reverbAmount = options.reverbAmount ?? 0;
13171
+ let unsubscribeClip;
12951
13172
  options.onReverbChange?.(reverbAmount);
12952
13173
  let drumVolume = options.drumVolume ?? 80;
12953
13174
  let currentDrumPattern = refs.drumSelect.value;
@@ -13028,7 +13249,9 @@ var mountDAW = (target, options = {}) => {
13028
13249
  vocalPan: t.vocalPan,
13029
13250
  vocalOctave: t.vocalOctave,
13030
13251
  vocalVibrato: t.vocalVibrato,
13031
- vocalReverb: t.vocalReverb
13252
+ vocalReverb: t.vocalReverb,
13253
+ vocalGender: t.vocalGender,
13254
+ vocalBreathiness: t.vocalBreathiness
13032
13255
  };
13033
13256
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
13034
13257
  lyricsDebounceTimer = setTimeout(() => {
@@ -13090,7 +13313,11 @@ var mountDAW = (target, options = {}) => {
13090
13313
  vocalOctave: 0,
13091
13314
  vocalVibrato: false,
13092
13315
  vocalReverb: 0,
13093
- trackInstrument: ""
13316
+ vocalGender: 50,
13317
+ vocalBreathiness: 50,
13318
+ trackInstrument: "",
13319
+ trackCompression: 0,
13320
+ trackWidth: 100
13094
13321
  };
13095
13322
  });
13096
13323
  };
@@ -13111,6 +13338,8 @@ var mountDAW = (target, options = {}) => {
13111
13338
  octave: t.vocalOctave,
13112
13339
  vibrato: t.vocalVibrato,
13113
13340
  reverb: t.vocalReverb,
13341
+ gender: t.vocalGender,
13342
+ breathiness: t.vocalBreathiness,
13114
13343
  syllables
13115
13344
  });
13116
13345
  });
@@ -13915,6 +14144,8 @@ var mountDAW = (target, options = {}) => {
13915
14144
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
13916
14145
  vibrato: lt.vibrato,
13917
14146
  reverbSend: (lt.reverb ?? 0) / 100,
14147
+ gender: (lt.gender ?? 50) / 100,
14148
+ breathiness: (lt.breathiness ?? 50) / 100,
13918
14149
  notes
13919
14150
  };
13920
14151
  }) : [];
@@ -14020,7 +14251,22 @@ var mountDAW = (target, options = {}) => {
14020
14251
  <span class="dtm-label">\u30D9\u30ED\u30B7\u30C6\u30A3</span>
14021
14252
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
14022
14253
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
14023
- </div>`;
14254
+ </div>
14255
+ <details class="dtm-advanced" data-dtm="track-fx-advanced">
14256
+ <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14257
+ <div class="dtm-row">
14258
+ <span class="dtm-label">\u97F3\u5727\u5F37\u5316</span>
14259
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-comp" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30B3\u30F3\u30D7\u30EC\u30C3\u30B5\u30FC\u91CF\uFF08\u97F3\u5727\u5F37\u5316\uFF09">
14260
+ <span class="dtm-label" data-dtm="track-comp-label"></span>
14261
+ <button class="dtm-infobtn" data-dtm="track-comp-info" title="\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14262
+ </div>
14263
+ <div class="dtm-row">
14264
+ <span class="dtm-label">\u30B9\u30C6\u30EC\u30AA\u5E45</span>
14265
+ <input type="range" class="dtm-range dtm-grow" data-dtm="track-width" min="0" max="200" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45\uFF08100=\u539F\u97F3\u30010=\u30E2\u30CE\u30E9\u30EB\uFF09">
14266
+ <span class="dtm-label" data-dtm="track-width-label"></span>
14267
+ <button class="dtm-infobtn" data-dtm="track-width-info" title="\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14268
+ </div>
14269
+ </details>`;
14024
14270
  const volInput = refs.trackBody.querySelector(
14025
14271
  '[data-dtm="track-vol"]'
14026
14272
  );
@@ -14037,6 +14283,47 @@ var mountDAW = (target, options = {}) => {
14037
14283
  volInput.title = active.lyricModel ? "\u6B4C\u8A5E\u30E2\u30FC\u30C9\u306E\u3068\u304D\u306F\u30D9\u30ED\u30B7\u30C6\u30A3\u304C\u518D\u751F\u306B\u53CD\u6620\u3055\u308C\u307E\u305B\u3093\uFF08\u58F0\u91CF\u3067\u8ABF\u6574\u3057\u3066\u304F\u3060\u3055\u3044\uFF09" : "";
14038
14284
  };
14039
14285
  syncVelocityDisabled();
14286
+ const trackCompInput = refs.trackBody.querySelector(
14287
+ '[data-dtm="track-comp"]'
14288
+ );
14289
+ const trackCompLabel = refs.trackBody.querySelector(
14290
+ '[data-dtm="track-comp-label"]'
14291
+ );
14292
+ const trackWidthInput = refs.trackBody.querySelector(
14293
+ '[data-dtm="track-width"]'
14294
+ );
14295
+ const trackWidthLabel = refs.trackBody.querySelector(
14296
+ '[data-dtm="track-width-label"]'
14297
+ );
14298
+ const trackCompInfo = refs.trackBody.querySelector(
14299
+ '[data-dtm="track-comp-info"]'
14300
+ );
14301
+ const trackWidthInfo = refs.trackBody.querySelector(
14302
+ '[data-dtm="track-width-info"]'
14303
+ );
14304
+ trackCompInput.value = String(active.trackCompression);
14305
+ trackCompLabel.textContent = `${active.trackCompression}%`;
14306
+ trackWidthInput.value = String(active.trackWidth);
14307
+ trackWidthLabel.textContent = `${active.trackWidth}%`;
14308
+ trackCompInput.addEventListener("input", () => {
14309
+ active.trackCompression = Number.parseInt(trackCompInput.value, 10);
14310
+ trackCompLabel.textContent = `${active.trackCompression}%`;
14311
+ options.onTrackCompressionChange?.(
14312
+ active.config.id,
14313
+ active.trackCompression
14314
+ );
14315
+ });
14316
+ trackWidthInput.addEventListener("input", () => {
14317
+ active.trackWidth = Number.parseInt(trackWidthInput.value, 10);
14318
+ trackWidthLabel.textContent = `${active.trackWidth}%`;
14319
+ options.onTrackWidthChange?.(active.config.id, active.trackWidth);
14320
+ });
14321
+ trackCompInfo.addEventListener("click", () => {
14322
+ showModal("\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC", TRACK_COMPRESSION_INFO_HTML);
14323
+ });
14324
+ trackWidthInfo.addEventListener("click", () => {
14325
+ showModal("\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC", TRACK_WIDTH_INFO_HTML);
14326
+ });
14040
14327
  const instRow = document.createElement("div");
14041
14328
  instRow.className = "dtm-row";
14042
14329
  instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
@@ -14100,17 +14387,6 @@ var mountDAW = (target, options = {}) => {
14100
14387
  <span class="dtm-label">\u266A UTAU</span>
14101
14388
  <select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
14102
14389
  <img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
14103
- <select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
14104
- <option value="2">+2 oct</option>
14105
- <option value="1">+1 oct</option>
14106
- <option value="0">\xB10 oct</option>
14107
- <option value="-1">-1 oct</option>
14108
- <option value="-2">-2 oct</option>
14109
- </select>
14110
- <label class="dtm-label" style="display:inline-flex;align-items:center;gap:2px;white-space:nowrap" title="\u30ED\u30F3\u30B0\u30C8\u30FC\u30F3\u306B\u81EA\u52D5\u3067\u30D3\u30D6\u30E9\u30FC\u30C8\u3092\u639B\u3051\u307E\u3059">
14111
- <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
14112
- </label>
14113
- <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14114
14390
  <span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
14115
14391
  </div>
14116
14392
  <div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
@@ -14136,16 +14412,48 @@ var mountDAW = (target, options = {}) => {
14136
14412
  <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-vol" min="0" max="${MAX_VOCAL_VOLUME}" aria-label="\u6B4C\u5531\u306E\u58F0\u91CF\uFF08100=\u7B49\u500D\u3001100\u8D85\u3067\u30D6\u30FC\u30B9\u30C8\u3001\u65E2\u5B9A200\uFF09">
14137
14413
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
14138
14414
  </div>
14139
- <div class="dtm-row">
14140
- <span class="dtm-label">\u5B9A\u4F4D</span>
14141
- <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-pan" min="0" max="127" aria-label="\u6B4C\u5531\u306E\u30B9\u30C6\u30EC\u30AA\u5B9A\u4F4D\uFF08\u5DE6\u53F3\uFF09">
14142
- <span class="dtm-label" data-dtm="lyric-pan-label"></span>
14143
- </div>
14144
- <div class="dtm-row">
14145
- <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14146
- <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-reverb" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304B\u3089\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u3078\u9001\u308B\u91CF\uFF08\u30DE\u30B9\u30BF\u306E\u30EA\u30D0\u30FC\u30D6\u3064\u307E\u307F\u304C0%\u3060\u3068\u7121\u97F3\uFF09" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u3078\u306E\u30BB\u30F3\u30C9\u91CF">
14147
- <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14148
- </div>
14415
+ <details class="dtm-advanced" data-dtm="lyric-advanced">
14416
+ <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
14417
+ <div class="dtm-row">
14418
+ <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
14419
+ <select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
14420
+ <option value="2">+2 oct</option>
14421
+ <option value="1">+1 oct</option>
14422
+ <option value="0">\xB10 oct</option>
14423
+ <option value="-1">-1 oct</option>
14424
+ <option value="-2">-2 oct</option>
14425
+ </select>
14426
+ </div>
14427
+ <div class="dtm-row">
14428
+ <span class="dtm-label">\u5B9A\u4F4D</span>
14429
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-pan" min="0" max="127" aria-label="\u6B4C\u5531\u306E\u30B9\u30C6\u30EC\u30AA\u5B9A\u4F4D\uFF08\u5DE6\u53F3\uFF09">
14430
+ <span class="dtm-label" data-dtm="lyric-pan-label"></span>
14431
+ </div>
14432
+ <div class="dtm-row">
14433
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14434
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-reverb" min="0" max="100" aria-label="\u3053\u306E\u30C8\u30E9\u30C3\u30AF\u304B\u3089\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u3078\u9001\u308B\u91CF\uFF08\u30DE\u30B9\u30BF\u306E\u30EA\u30D0\u30FC\u30D6\u3064\u307E\u307F\u304C0%\u3060\u3068\u7121\u97F3\uFF09">
14435
+ <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14436
+ <button class="dtm-infobtn" data-dtm="lyric-reverb-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14437
+ </div>
14438
+ <div class="dtm-row">
14439
+ <span class="dtm-label">\u30B8\u30A7\u30F3\u30C0\u30FC</span>
14440
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-gender" min="0" max="100" aria-label="\u30D5\u30A9\u30EB\u30DE\u30F3\u30C8/\u30B8\u30A7\u30F3\u30C0\u30FC\u30D5\u30A1\u30AF\u30BF\u30FC\uFF08koe\u97F3\u6E90\u9650\u5B9A\uFF09">
14441
+ <span class="dtm-label" data-dtm="lyric-gender-label"></span>
14442
+ <button class="dtm-infobtn" data-dtm="lyric-gender-info" title="\u30B8\u30A7\u30F3\u30C0\u30FC\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14443
+ </div>
14444
+ <div class="dtm-row">
14445
+ <span class="dtm-label">\u30D6\u30EC\u30B7\u30CD\u30B9</span>
14446
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-breathiness" min="0" max="100" aria-label="\u30D6\u30EC\u30B7\u30CD\u30B9\uFF08\u606F\u6210\u5206\u3001koe\u97F3\u6E90\u9650\u5B9A\uFF09">
14447
+ <span class="dtm-label" data-dtm="lyric-breathiness-label"></span>
14448
+ <button class="dtm-infobtn" data-dtm="lyric-breathiness-info" title="\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14449
+ </div>
14450
+ <div class="dtm-row">
14451
+ <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14452
+ <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
14453
+ </span>
14454
+ <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14455
+ </div>
14456
+ </details>
14149
14457
  <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>
14150
14458
  </div>`;
14151
14459
  refs.trackBody.appendChild(lyricDiv);
@@ -14185,6 +14493,36 @@ var mountDAW = (target, options = {}) => {
14185
14493
  const lyricReverbLabel = lyricDiv.querySelector(
14186
14494
  '[data-dtm="lyric-reverb-label"]'
14187
14495
  );
14496
+ const lyricReverbInfo = lyricDiv.querySelector(
14497
+ '[data-dtm="lyric-reverb-info"]'
14498
+ );
14499
+ lyricReverbInfo.addEventListener("click", () => {
14500
+ showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_REVERB_INFO_HTML);
14501
+ });
14502
+ const lyricGender = lyricDiv.querySelector(
14503
+ '[data-dtm="lyric-gender"]'
14504
+ );
14505
+ const lyricGenderLabel = lyricDiv.querySelector(
14506
+ '[data-dtm="lyric-gender-label"]'
14507
+ );
14508
+ const lyricGenderInfo = lyricDiv.querySelector(
14509
+ '[data-dtm="lyric-gender-info"]'
14510
+ );
14511
+ lyricGenderInfo.addEventListener("click", () => {
14512
+ showModal("\u30B8\u30A7\u30F3\u30C0\u30FC\u306E\u89E3\u8AAC", GENDER_INFO_HTML);
14513
+ });
14514
+ const lyricBreathiness = lyricDiv.querySelector(
14515
+ '[data-dtm="lyric-breathiness"]'
14516
+ );
14517
+ const lyricBreathinessLabel = lyricDiv.querySelector(
14518
+ '[data-dtm="lyric-breathiness-label"]'
14519
+ );
14520
+ const lyricBreathinessInfo = lyricDiv.querySelector(
14521
+ '[data-dtm="lyric-breathiness-info"]'
14522
+ );
14523
+ lyricBreathinessInfo.addEventListener("click", () => {
14524
+ showModal("\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC", BREATHINESS_INFO_HTML);
14525
+ });
14188
14526
  const lyricVibrato = lyricDiv.querySelector(
14189
14527
  '[data-dtm="lyric-vibrato"]'
14190
14528
  );
@@ -14257,6 +14595,10 @@ var mountDAW = (target, options = {}) => {
14257
14595
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14258
14596
  lyricReverb.value = String(active.vocalReverb);
14259
14597
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14598
+ lyricGender.value = String(active.vocalGender);
14599
+ lyricGenderLabel.textContent = `${active.vocalGender}`;
14600
+ lyricBreathiness.value = String(active.vocalBreathiness);
14601
+ lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14260
14602
  lyricVibrato.checked = active.vocalVibrato;
14261
14603
  const updateLyricCount = () => {
14262
14604
  const n = normalizeLyrics(lyricInput.value).length;
@@ -14307,7 +14649,6 @@ var mountDAW = (target, options = {}) => {
14307
14649
  };
14308
14650
  const syncLyricVisibility = () => {
14309
14651
  lyricBody.style.display = active.lyricModel ? "" : "none";
14310
- lyricOctaveSel.style.display = active.lyricModel ? "" : "none";
14311
14652
  updateLyricCount();
14312
14653
  syncLyricTerms();
14313
14654
  syncLyricIcon();
@@ -14408,6 +14749,16 @@ var mountDAW = (target, options = {}) => {
14408
14749
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14409
14750
  fireLyricsChange(active);
14410
14751
  });
14752
+ lyricGender.addEventListener("input", () => {
14753
+ active.vocalGender = Number.parseInt(lyricGender.value, 10);
14754
+ lyricGenderLabel.textContent = `${active.vocalGender}`;
14755
+ fireLyricsChange(active);
14756
+ });
14757
+ lyricBreathiness.addEventListener("input", () => {
14758
+ active.vocalBreathiness = Number.parseInt(lyricBreathiness.value, 10);
14759
+ lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14760
+ fireLyricsChange(active);
14761
+ });
14411
14762
  }
14412
14763
  if (active.config.id === "chord" && showChord) {
14413
14764
  const div = document.createElement("div");
@@ -14491,18 +14842,28 @@ var mountDAW = (target, options = {}) => {
14491
14842
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
14492
14843
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
14493
14844
  const trackInstrumentsForMeta = {};
14845
+ const trackCompressionForMeta = {};
14846
+ const trackWidthForMeta = {};
14494
14847
  trackStates.forEach((t, i2) => {
14495
14848
  if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14849
+ if (t.trackCompression !== 0)
14850
+ trackCompressionForMeta[i2] = t.trackCompression;
14851
+ if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
14496
14852
  });
14497
14853
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14854
+ const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
14855
+ const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
14498
14856
  const metaLineFull = formatMmlMeta(
14499
14857
  {
14500
14858
  instrument: currentInstrument || void 0,
14501
14859
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
14502
14860
  volume: masterVolume,
14503
14861
  drumVolume,
14862
+ reverb: reverbAmount,
14504
14863
  mode,
14505
- trackInstruments: trackInstMeta
14864
+ trackInstruments: trackInstMeta,
14865
+ trackCompression: trackCompMeta,
14866
+ trackWidth: trackWidthMeta
14506
14867
  },
14507
14868
  " "
14508
14869
  );
@@ -14512,8 +14873,11 @@ var mountDAW = (target, options = {}) => {
14512
14873
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
14513
14874
  volume: masterVolume,
14514
14875
  drumVolume,
14876
+ reverb: reverbAmount,
14515
14877
  mode,
14516
- trackInstruments: trackInstMeta
14878
+ trackInstruments: trackInstMeta,
14879
+ trackCompression: trackCompMeta,
14880
+ trackWidth: trackWidthMeta
14517
14881
  },
14518
14882
  ""
14519
14883
  );
@@ -14562,7 +14926,9 @@ var mountDAW = (target, options = {}) => {
14562
14926
  pan: t.vocalPan,
14563
14927
  oct: t.vocalOctave,
14564
14928
  vib: t.vocalVibrato,
14565
- rev: t.vocalReverb
14929
+ rev: t.vocalReverb,
14930
+ gen: t.vocalGender,
14931
+ bre: t.vocalBreathiness
14566
14932
  })).filter(
14567
14933
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
14568
14934
  ).map((x2) => {
@@ -14572,7 +14938,9 @@ var mountDAW = (target, options = {}) => {
14572
14938
  x2.pan === 64 ? "" : `p${x2.pan}`,
14573
14939
  x2.oct === 0 ? "" : `o${x2.oct}`,
14574
14940
  x2.vib ? "b1" : "",
14575
- x2.rev === 0 ? "" : `r${x2.rev}`
14941
+ x2.rev === 0 ? "" : `r${x2.rev}`,
14942
+ x2.gen === 50 ? "" : `g${x2.gen}`,
14943
+ x2.bre === 50 ? "" : `h${x2.bre}`
14576
14944
  ].filter((s) => s.length > 0).join(" ");
14577
14945
  const head = params ? `${x2.model} ${params}` : x2.model;
14578
14946
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -14712,6 +15080,12 @@ var mountDAW = (target, options = {}) => {
14712
15080
  refs.drumVolume.value = String(meta.drumVolume);
14713
15081
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
14714
15082
  }
15083
+ if (meta.reverb !== void 0) {
15084
+ reverbAmount = meta.reverb;
15085
+ refs.reverbAmount.value = String(meta.reverb);
15086
+ refs.reverbAmountLabel.textContent = `${meta.reverb}%`;
15087
+ options.onReverbChange?.(meta.reverb);
15088
+ }
14715
15089
  }
14716
15090
  trackStates.forEach((t, i2) => {
14717
15091
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -14721,6 +15095,19 @@ var mountDAW = (target, options = {}) => {
14721
15095
  options.onTrackInstrumentChange?.(i2, name);
14722
15096
  }
14723
15097
  });
15098
+ trackStates.forEach((t, i2) => {
15099
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
15100
+ const comp = meta.trackCompression?.[i2] ?? 0;
15101
+ if (t.trackCompression !== comp) {
15102
+ t.trackCompression = comp;
15103
+ options.onTrackCompressionChange?.(t.config.id, comp);
15104
+ }
15105
+ const width = meta.trackWidth?.[i2] ?? 100;
15106
+ if (t.trackWidth !== width) {
15107
+ t.trackWidth = width;
15108
+ options.onTrackWidthChange?.(t.config.id, width);
15109
+ }
15110
+ });
14724
15111
  trackStates.forEach((t, i2) => {
14725
15112
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
14726
15113
  const v = trackVelocity.get(i2);
@@ -14739,6 +15126,8 @@ var mountDAW = (target, options = {}) => {
14739
15126
  active.vocalOctave = 0;
14740
15127
  active.vocalVibrato = false;
14741
15128
  active.vocalReverb = 0;
15129
+ active.vocalGender = 50;
15130
+ active.vocalBreathiness = 50;
14742
15131
  } else {
14743
15132
  for (const t of trackStates) {
14744
15133
  t.lyrics = "";
@@ -14749,6 +15138,8 @@ var mountDAW = (target, options = {}) => {
14749
15138
  t.vocalOctave = 0;
14750
15139
  t.vocalVibrato = false;
14751
15140
  t.vocalReverb = 0;
15141
+ t.vocalGender = 50;
15142
+ t.vocalBreathiness = 50;
14752
15143
  }
14753
15144
  }
14754
15145
  lyrics?.forEach((lt) => {
@@ -14763,6 +15154,8 @@ var mountDAW = (target, options = {}) => {
14763
15154
  t.vocalOctave = lt.octave ?? 0;
14764
15155
  t.vocalVibrato = lt.vibrato ?? false;
14765
15156
  t.vocalReverb = lt.reverb ?? 0;
15157
+ t.vocalGender = lt.gender ?? 50;
15158
+ t.vocalBreathiness = lt.breathiness ?? 50;
14766
15159
  });
14767
15160
  for (const p of placements) {
14768
15161
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -14843,6 +15236,8 @@ var mountDAW = (target, options = {}) => {
14843
15236
  active.vocalOctave = 0;
14844
15237
  active.vocalVibrato = false;
14845
15238
  active.vocalReverb = 0;
15239
+ active.vocalGender = 50;
15240
+ active.vocalBreathiness = 50;
14846
15241
  } else {
14847
15242
  clearAll();
14848
15243
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -14855,6 +15250,8 @@ var mountDAW = (target, options = {}) => {
14855
15250
  t.vocalOctave = 0;
14856
15251
  t.vocalVibrato = false;
14857
15252
  t.vocalReverb = 0;
15253
+ t.vocalGender = 50;
15254
+ t.vocalBreathiness = 50;
14858
15255
  }
14859
15256
  }
14860
15257
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -14989,6 +15386,12 @@ var mountDAW = (target, options = {}) => {
14989
15386
  isSolo = refs.soloCheckbox.checked;
14990
15387
  redrawAll();
14991
15388
  });
15389
+ unsubscribeClip = options.clipMeter?.onClipChange((clipping) => {
15390
+ refs.clipBadge.classList.toggle("dtm-hidden", !clipping);
15391
+ });
15392
+ refs.clipBadge.addEventListener("click", () => {
15393
+ options.clipMeter?.reset();
15394
+ });
14992
15395
  refs.toolPen.addEventListener("click", () => setToolMode("pen"));
14993
15396
  refs.toolSelect.addEventListener("click", () => setToolMode("select"));
14994
15397
  refs.toolEraser.addEventListener("click", () => setToolMode("eraser"));
@@ -15069,6 +15472,30 @@ var mountDAW = (target, options = {}) => {
15069
15472
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
15070
15473
  options.onReverbChange?.(reverbAmount);
15071
15474
  });
15475
+ refs.reverbAmountInfoBtn.addEventListener("click", () => {
15476
+ showModal("\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC", MASTER_REVERB_INFO_HTML);
15477
+ });
15478
+ refs.autoMasterInfoBtn.addEventListener("click", () => {
15479
+ showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15480
+ });
15481
+ refs.autoMasterBtn.addEventListener("click", () => {
15482
+ reverbAmount = 20;
15483
+ refs.reverbAmount.value = "20";
15484
+ refs.reverbAmountLabel.textContent = "20%";
15485
+ options.onReverbChange?.(20);
15486
+ for (const t of trackStates) {
15487
+ t.trackCompression = 35;
15488
+ t.trackWidth = 115;
15489
+ options.onTrackCompressionChange?.(t.config.id, 35);
15490
+ options.onTrackWidthChange?.(t.config.id, 115);
15491
+ if (t.lyricModel) {
15492
+ t.vocalVibrato = true;
15493
+ t.vocalReverb = 25;
15494
+ fireLyricsChange(t);
15495
+ }
15496
+ }
15497
+ updateTrackPanel();
15498
+ });
15072
15499
  refs.drumSelect.addEventListener("change", () => {
15073
15500
  currentDrumPattern = refs.drumSelect.value;
15074
15501
  options.onDrumChange?.(currentDrumPattern);
@@ -16080,6 +16507,8 @@ var mountDAW = (target, options = {}) => {
16080
16507
  t.vocalOctave = data.vocalOctave;
16081
16508
  t.vocalVibrato = data.vocalVibrato ?? false;
16082
16509
  t.vocalReverb = data.vocalReverb ?? 0;
16510
+ t.vocalGender = data.vocalGender ?? 50;
16511
+ t.vocalBreathiness = data.vocalBreathiness ?? 50;
16083
16512
  },
16084
16513
  applyTrackInstrument: (trackIndex, instrumentName) => {
16085
16514
  const t = trackStates[trackIndex];
@@ -16105,6 +16534,7 @@ var mountDAW = (target, options = {}) => {
16105
16534
  destroy: () => {
16106
16535
  sequencer.stop();
16107
16536
  options.singingVoices?.stopStream();
16537
+ unsubscribeClip?.();
16108
16538
  resizeObserver.disconnect();
16109
16539
  document.removeEventListener("pointermove", onPointerMove);
16110
16540
  document.removeEventListener("pointerup", onPointerUp);
@@ -16197,6 +16627,8 @@ var playSingingMML = async (mml, options = {}) => {
16197
16627
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16198
16628
  vibrato: lt.vibrato,
16199
16629
  reverbSend: (lt.reverb ?? 0) / 100,
16630
+ gender: (lt.gender ?? 50) / 100,
16631
+ breathiness: (lt.breathiness ?? 50) / 100,
16200
16632
  notes
16201
16633
  };
16202
16634
  });
@@ -16804,6 +17236,147 @@ var isSupported = midiJsonParser.isSupported;
16804
17236
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
16805
17237
  URL.revokeObjectURL(url);
16806
17238
 
17239
+ // src/channel-strip.ts
17240
+ var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17241
+ var compressionParams = (amount) => {
17242
+ const t = clamp4(amount, 0, 100) / 100;
17243
+ return {
17244
+ threshold: 0 + (-24 - 0) * t,
17245
+ ratio: 1 + (12 - 1) * t,
17246
+ knee: 0 + (6 - 0) * t,
17247
+ attack: 0.02 + (3e-3 - 0.02) * t,
17248
+ release: 0.25 + (0.15 - 0.25) * t
17249
+ };
17250
+ };
17251
+ var createChannelStrip = (ctx, destination, options = {}) => {
17252
+ const input = ctx.createGain();
17253
+ const compressor = ctx.createDynamicsCompressor();
17254
+ const applyCompression = (amount) => {
17255
+ const p = compressionParams(amount);
17256
+ const now = ctx.currentTime;
17257
+ compressor.threshold.setValueAtTime(p.threshold, now);
17258
+ compressor.ratio.setValueAtTime(p.ratio, now);
17259
+ compressor.knee.setValueAtTime(p.knee, now);
17260
+ compressor.attack.setValueAtTime(p.attack, now);
17261
+ compressor.release.setValueAtTime(p.release, now);
17262
+ };
17263
+ applyCompression(options.compression ?? 0);
17264
+ const splitter = ctx.createChannelSplitter(2);
17265
+ const mid = ctx.createGain();
17266
+ mid.gain.value = 0.5;
17267
+ splitter.connect(mid, 0);
17268
+ splitter.connect(mid, 1);
17269
+ const sideSum = ctx.createGain();
17270
+ sideSum.gain.value = 1;
17271
+ const sideL = ctx.createGain();
17272
+ sideL.gain.value = 0.5;
17273
+ const sideR = ctx.createGain();
17274
+ sideR.gain.value = -0.5;
17275
+ splitter.connect(sideL, 0);
17276
+ splitter.connect(sideR, 1);
17277
+ sideL.connect(sideSum);
17278
+ sideR.connect(sideSum);
17279
+ const widthGain = ctx.createGain();
17280
+ sideSum.connect(widthGain);
17281
+ const widthInv = ctx.createGain();
17282
+ widthInv.gain.value = -1;
17283
+ widthGain.connect(widthInv);
17284
+ const outL = ctx.createGain();
17285
+ mid.connect(outL);
17286
+ widthGain.connect(outL);
17287
+ const outR = ctx.createGain();
17288
+ mid.connect(outR);
17289
+ widthInv.connect(outR);
17290
+ const merger = ctx.createChannelMerger(2);
17291
+ outL.connect(merger, 0, 0);
17292
+ outR.connect(merger, 0, 1);
17293
+ const setWidth = (width) => {
17294
+ widthGain.gain.setTargetAtTime(
17295
+ clamp4(width, 0, 200) / 100,
17296
+ ctx.currentTime,
17297
+ 0.02
17298
+ );
17299
+ };
17300
+ setWidth(options.width ?? 100);
17301
+ input.connect(compressor);
17302
+ compressor.connect(splitter);
17303
+ merger.connect(destination);
17304
+ return {
17305
+ input,
17306
+ setCompression: applyCompression,
17307
+ setWidth,
17308
+ dispose: () => {
17309
+ input.disconnect();
17310
+ compressor.disconnect();
17311
+ splitter.disconnect();
17312
+ mid.disconnect();
17313
+ sideSum.disconnect();
17314
+ sideL.disconnect();
17315
+ sideR.disconnect();
17316
+ widthGain.disconnect();
17317
+ widthInv.disconnect();
17318
+ outL.disconnect();
17319
+ outR.disconnect();
17320
+ merger.disconnect();
17321
+ }
17322
+ };
17323
+ };
17324
+
17325
+ // src/clip-meter.ts
17326
+ var createClipMeter = (ctx, source, options = {}) => {
17327
+ const threshold = options.threshold ?? 0.98;
17328
+ const holdMs = options.holdMs ?? 800;
17329
+ const analyser = ctx.createAnalyser();
17330
+ analyser.fftSize = 512;
17331
+ source.connect(analyser);
17332
+ const buf = new Float32Array(analyser.fftSize);
17333
+ let clipping = false;
17334
+ let clipUntil = 0;
17335
+ let peakLevel = 0;
17336
+ const listeners = /* @__PURE__ */ new Set();
17337
+ let rafId = null;
17338
+ const tick = () => {
17339
+ analyser.getFloatTimeDomainData(buf);
17340
+ let peak = 0;
17341
+ let clipped = false;
17342
+ for (let i2 = 0; i2 < buf.length; i2++) {
17343
+ const v = Math.abs(buf[i2]);
17344
+ if (v > peak) peak = v;
17345
+ if (v >= threshold) clipped = true;
17346
+ }
17347
+ peakLevel = peak;
17348
+ const now = performance.now();
17349
+ if (clipped) clipUntil = now + holdMs;
17350
+ const nextClipping = now < clipUntil;
17351
+ if (nextClipping !== clipping) {
17352
+ clipping = nextClipping;
17353
+ for (const cb of listeners) cb(clipping);
17354
+ }
17355
+ rafId = requestAnimationFrame(tick);
17356
+ };
17357
+ rafId = requestAnimationFrame(tick);
17358
+ return {
17359
+ onClipChange: (cb) => {
17360
+ listeners.add(cb);
17361
+ return () => listeners.delete(cb);
17362
+ },
17363
+ getPeakLevel: () => peakLevel,
17364
+ reset: () => {
17365
+ clipUntil = 0;
17366
+ if (clipping) {
17367
+ clipping = false;
17368
+ for (const cb of listeners) cb(false);
17369
+ }
17370
+ },
17371
+ dispose: () => {
17372
+ if (rafId !== null) cancelAnimationFrame(rafId);
17373
+ rafId = null;
17374
+ analyser.disconnect();
17375
+ listeners.clear();
17376
+ }
17377
+ };
17378
+ };
17379
+
16807
17380
  // src/reverb.ts
16808
17381
  var IMPULSE_DURATION_SEC = 2.2;
16809
17382
  var IMPULSE_DECAY = 2.5;
@@ -16975,7 +17548,6 @@ var createDtmStudio = async (options = {}) => {
16975
17548
  const audioCtx = options.audioContext ?? new AudioContext({ sampleRate: 44100 });
16976
17549
  const masterGain = audioCtx.createGain();
16977
17550
  masterGain.gain.value = options.masterVolume ?? 1;
16978
- masterGain.connect(audioCtx.destination);
16979
17551
  const drumGain = audioCtx.createGain();
16980
17552
  drumGain.gain.value = options.drumVolume ?? 1;
16981
17553
  drumGain.connect(masterGain);
@@ -16986,7 +17558,6 @@ var createDtmStudio = async (options = {}) => {
16986
17558
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
16987
17559
  masterGain.connect(reverbConvolver);
16988
17560
  reverbConvolver.connect(reverbWetGain);
16989
- reverbWetGain.connect(audioCtx.destination);
16990
17561
  const setReverbAmount = (amount) => {
16991
17562
  reverbWetGain.gain.setTargetAtTime(
16992
17563
  reverbAmountToGain(amount),
@@ -16994,6 +17565,27 @@ var createDtmStudio = async (options = {}) => {
16994
17565
  0.02
16995
17566
  );
16996
17567
  };
17568
+ const finalMix = audioCtx.createGain();
17569
+ masterGain.connect(finalMix);
17570
+ reverbWetGain.connect(finalMix);
17571
+ const safetyLimiter = audioCtx.createDynamicsCompressor();
17572
+ safetyLimiter.threshold.value = -1;
17573
+ safetyLimiter.knee.value = 0;
17574
+ safetyLimiter.ratio.value = 20;
17575
+ safetyLimiter.attack.value = 1e-3;
17576
+ safetyLimiter.release.value = 0.1;
17577
+ finalMix.connect(safetyLimiter);
17578
+ safetyLimiter.connect(options.destination ?? audioCtx.destination);
17579
+ const clipMeter = createClipMeter(audioCtx, finalMix);
17580
+ const channelStrips = /* @__PURE__ */ new Map();
17581
+ const getChannelStrip = (trackId) => {
17582
+ let strip = channelStrips.get(trackId);
17583
+ if (!strip) {
17584
+ strip = createChannelStrip(audioCtx, masterGain);
17585
+ channelStrips.set(trackId, strip);
17586
+ }
17587
+ return strip;
17588
+ };
16997
17589
  const resumeAudio = () => {
16998
17590
  if (audioCtx.state === "closed") return Promise.resolve();
16999
17591
  return audioCtx.resume();
@@ -17027,7 +17619,11 @@ var createDtmStudio = async (options = {}) => {
17027
17619
  worldlineScriptUrl: options.worldlineScriptUrl,
17028
17620
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
17029
17621
  // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
17030
- reverbBus: reverbConvolver
17622
+ reverbBus: reverbConvolver,
17623
+ // トラック単位チャンネルストリップ(コンプレッサー/ステレオワイド)の入口。
17624
+ // 楽器と同じ getChannelStrip キャッシュを共有するので、演奏トラックと歌詞トラックが
17625
+ // 同じ trackId を指していれば同じ処理が掛かる。
17626
+ getTrackDestination: (trackId) => getChannelStrip(trackId).input
17031
17627
  });
17032
17628
  const listReady = new Promise((resolve) => {
17033
17629
  sfList.init();
@@ -17257,7 +17853,7 @@ var createDtmStudio = async (options = {}) => {
17257
17853
  if (!sfInst) return;
17258
17854
  sfInst.play({
17259
17855
  ctx: audioCtx,
17260
- destination: masterGain,
17856
+ destination: getChannelStrip(e.trackId).input,
17261
17857
  pitch: e.pitch,
17262
17858
  volume: e.volume,
17263
17859
  when: e.when,
@@ -17304,6 +17900,9 @@ var createDtmStudio = async (options = {}) => {
17304
17900
  },
17305
17901
  reverbAmount: options.reverbAmount,
17306
17902
  onReverbChange: setReverbAmount,
17903
+ onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
17904
+ onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
17905
+ clipMeter,
17307
17906
  ...dawOverrides,
17308
17907
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
17309
17908
  onDrumChange: (name) => {
@@ -17330,17 +17929,22 @@ var createDtmStudio = async (options = {}) => {
17330
17929
  mountedEditors.push(daw);
17331
17930
  const wantPresetUI = presetUI ?? features.presetUI;
17332
17931
  const rollEl = target.querySelector('[data-dtm="roll"]');
17932
+ const presetSlot = target.querySelector(
17933
+ '[data-dtm="preset-select-slot"]'
17934
+ );
17333
17935
  if (wantPresetUI) {
17334
17936
  editorPresetSelects.get(target)?.destroy();
17335
- presetSelect = mountPresetSelect(target, {
17937
+ presetSelect = mountPresetSelect(presetSlot ?? target, {
17336
17938
  getDaw: () => daw,
17337
17939
  getTrackIds: () => trackIds,
17338
17940
  value: initialPreset,
17339
17941
  loadingTarget: rollEl ?? target,
17340
- position: "prepend",
17341
- // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
17942
+ position: presetSlot ? "append" : "prepend",
17943
+ // 楽器変更時、このエディタの発音解決が使うプリセットも追従させ、
17944
+ // 外部の onInstrumentChange(呼び出し側の永続化フック等)へも通知する。
17945
+ // #inst= 付きMML読込時の通知経路(handleInstrumentChange)と揃える。
17342
17946
  onChange: (key) => {
17343
- editorPreset = key;
17947
+ handleInstrumentChange(key);
17344
17948
  }
17345
17949
  });
17346
17950
  editorPresetSelects.set(target, presetSelect);
@@ -17523,7 +18127,7 @@ var createDtmStudio = async (options = {}) => {
17523
18127
  if (!sfInst) return;
17524
18128
  sfInst.play({
17525
18129
  ctx: audioCtx,
17526
- destination: masterGain,
18130
+ destination: getChannelStrip(e.trackId).input,
17527
18131
  pitch: e.pitch,
17528
18132
  volume: e.volume,
17529
18133
  when: e.when,
@@ -17609,7 +18213,7 @@ var createDtmStudio = async (options = {}) => {
17609
18213
  if (!sfInst) return;
17610
18214
  sfInst.play({
17611
18215
  ctx: audioCtx,
17612
- destination: masterGain,
18216
+ destination: getChannelStrip(e.trackId).input,
17613
18217
  pitch: e.pitch,
17614
18218
  volume: e.volume,
17615
18219
  when: e.when,
@@ -17688,7 +18292,7 @@ var createDtmStudio = async (options = {}) => {
17688
18292
  if (!sfInst) return;
17689
18293
  sfInst.play({
17690
18294
  ctx: audioCtx,
17691
- destination: masterGain,
18295
+ destination: getChannelStrip(e.trackId).input,
17692
18296
  pitch: e.pitch,
17693
18297
  volume: e.volume,
17694
18298
  when: e.when,
@@ -17727,7 +18331,7 @@ var createDtmStudio = async (options = {}) => {
17727
18331
  if (!sfInst) return;
17728
18332
  sfInst.play({
17729
18333
  ctx: audioCtx,
17730
- destination: masterGain,
18334
+ destination: getChannelStrip(e.trackId).input,
17731
18335
  pitch: e.pitch,
17732
18336
  volume: e.volume,
17733
18337
  when: e.when,
@@ -17783,7 +18387,7 @@ var createDtmStudio = async (options = {}) => {
17783
18387
  if (!sfInst) return;
17784
18388
  sfInst.play({
17785
18389
  ctx: audioCtx,
17786
- destination: masterGain,
18390
+ destination: getChannelStrip("chord").input,
17787
18391
  pitch: e.pitch,
17788
18392
  volume: e.volume,
17789
18393
  when: e.when,
@@ -17833,6 +18437,9 @@ var createDtmStudio = async (options = {}) => {
17833
18437
  mountedPlayers.length = 0;
17834
18438
  mountedChordPlayers.length = 0;
17835
18439
  mountedEditors.length = 0;
18440
+ clipMeter.dispose();
18441
+ for (const strip of channelStrips.values()) strip.dispose();
18442
+ channelStrips.clear();
17836
18443
  void audioCtx.close();
17837
18444
  };
17838
18445
  const setMasterVolume = (volume) => {