@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.mjs CHANGED
@@ -2186,8 +2186,10 @@ var parseLyrics = (mml) => {
2186
2186
  let octave = 0;
2187
2187
  let vibrato = false;
2188
2188
  let reverb = 0;
2189
+ let gender = 50;
2190
+ let breathiness = 50;
2189
2191
  const modelMatch = rest.match(
2190
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobr]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2192
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrgh]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2191
2193
  );
2192
2194
  let model = "";
2193
2195
  const metaTokens = [];
@@ -2242,6 +2244,20 @@ var parseLyrics = (mml) => {
2242
2244
  rest = rest.substring(rMatch[0].length).trim();
2243
2245
  continue;
2244
2246
  }
2247
+ const gMatch = rest.match(/^g(\d+)/i);
2248
+ if (gMatch) {
2249
+ gender = clamp(Number.parseInt(gMatch[1], 10), 0, 100);
2250
+ metaTokens.push(gMatch[0]);
2251
+ rest = rest.substring(gMatch[0].length).trim();
2252
+ continue;
2253
+ }
2254
+ const hMatch = rest.match(/^h(\d+)/i);
2255
+ if (hMatch) {
2256
+ breathiness = clamp(Number.parseInt(hMatch[1], 10), 0, 100);
2257
+ metaTokens.push(hMatch[0]);
2258
+ rest = rest.substring(hMatch[0].length).trim();
2259
+ continue;
2260
+ }
2245
2261
  break;
2246
2262
  }
2247
2263
  const lyricLines = [rest];
@@ -2258,6 +2274,8 @@ var parseLyrics = (mml) => {
2258
2274
  octave,
2259
2275
  vibrato,
2260
2276
  reverb,
2277
+ gender,
2278
+ breathiness,
2261
2279
  syllables,
2262
2280
  metaText: metaTokens.join(" "),
2263
2281
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -2353,12 +2371,13 @@ var createKlattVoice = (ctx, destination, reverbBus) => {
2353
2371
  const attack = 0.02;
2354
2372
  const release = 0.06;
2355
2373
  const sustainEnd = t0 + Math.max(attack + 0.02, e.duration);
2374
+ const dest = e.destination ?? destination;
2356
2375
  let panner = null;
2357
- let out = destination;
2376
+ let out = dest;
2358
2377
  if (typeof ctx.createStereoPanner === "function") {
2359
2378
  panner = ctx.createStereoPanner();
2360
2379
  panner.pan.value = Math.max(-1, Math.min(1, e.pan ?? 0));
2361
- panner.connect(destination);
2380
+ panner.connect(dest);
2362
2381
  out = panner;
2363
2382
  }
2364
2383
  let sendGain = null;
@@ -2586,7 +2605,7 @@ var createLocalBackend = async (options) => {
2586
2605
  }
2587
2606
  return p;
2588
2607
  };
2589
- const renderAlias = async (alias, pitch, durationMs, vibrato) => {
2608
+ const renderAlias = async (alias, pitch, durationMs, vibrato, expr) => {
2590
2609
  const pcm = await getPcm(alias);
2591
2610
  if (!pcm || pcm.length === 0) return null;
2592
2611
  const entry = bank.manifest.phonemes[alias];
@@ -2597,7 +2616,9 @@ var createLocalBackend = async (options) => {
2597
2616
  pcm,
2598
2617
  pitch: vibrato ? vibratoPitchCurve(targetHz, lead.preMs) : targetHz,
2599
2618
  durationMs,
2600
- ...lead
2619
+ ...lead,
2620
+ gender: expr?.gender,
2621
+ breathiness: expr?.breathiness
2601
2622
  });
2602
2623
  if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
2603
2624
  }
@@ -2662,7 +2683,7 @@ var createWorkerBackend = async (workerUrl, options) => {
2662
2683
  });
2663
2684
  onReady = null;
2664
2685
  onFail = null;
2665
- const renderAlias = (alias, pitch, durationMs, vibrato) => new Promise((resolve) => {
2686
+ const renderAlias = (alias, pitch, durationMs, vibrato, expr) => new Promise((resolve) => {
2666
2687
  const id = ++reqId;
2667
2688
  pending.set(
2668
2689
  id,
@@ -2676,7 +2697,9 @@ var createWorkerBackend = async (workerUrl, options) => {
2676
2697
  alias,
2677
2698
  pitch,
2678
2699
  durationMs,
2679
- vibrato
2700
+ vibrato,
2701
+ gender: expr?.gender,
2702
+ breathiness: expr?.breathiness
2680
2703
  });
2681
2704
  });
2682
2705
  return {
@@ -2705,15 +2728,21 @@ var createKoeVoice = async (ctx, destination, options) => {
2705
2728
  const inflight = /* @__PURE__ */ new Map();
2706
2729
  const active = /* @__PURE__ */ new Set();
2707
2730
  let prevVowel = "";
2708
- const keyOf = (alias, pitch, durationMs, vibrato) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}${vibrato ? "|vib" : ""}`;
2709
- const renderInto = (alias, pitch, durationMs, vibrato) => {
2710
- const key = keyOf(alias, pitch, durationMs, vibrato);
2731
+ 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)}` : ""}`;
2732
+ const renderInto = (alias, pitch, durationMs, vibrato, expr) => {
2733
+ const key = keyOf(alias, pitch, durationMs, vibrato, expr);
2711
2734
  const existing = renderCache.get(key);
2712
2735
  if (existing !== void 0) return Promise.resolve(existing);
2713
2736
  const flying = inflight.get(key);
2714
2737
  if (flying) return flying;
2715
2738
  const p = (async () => {
2716
- const out = await backend.renderAlias(alias, pitch, durationMs, vibrato);
2739
+ const out = await backend.renderAlias(
2740
+ alias,
2741
+ pitch,
2742
+ durationMs,
2743
+ vibrato,
2744
+ expr
2745
+ );
2717
2746
  let rendered = null;
2718
2747
  if (out) {
2719
2748
  const buf = ctx.createBuffer(1, out.pcm.length, KOE_SAMPLE_RATE);
@@ -2728,13 +2757,14 @@ var createKoeVoice = async (ctx, destination, options) => {
2728
2757
  return p;
2729
2758
  };
2730
2759
  const LEADCAP_S = 0.09;
2731
- const schedule = (r, t0, peak, pan, reverbSend = 0) => {
2732
- let out = destination;
2760
+ const schedule = (r, t0, peak, pan, reverbSend = 0, destOverride) => {
2761
+ const dest = destOverride ?? destination;
2762
+ let out = dest;
2733
2763
  let panner = null;
2734
2764
  if (typeof ctx.createStereoPanner === "function") {
2735
2765
  panner = ctx.createStereoPanner();
2736
2766
  panner.pan.value = Math.max(-1, Math.min(1, pan));
2737
- panner.connect(destination);
2767
+ panner.connect(dest);
2738
2768
  out = panner;
2739
2769
  }
2740
2770
  let sendGain = null;
@@ -2787,10 +2817,10 @@ var createKoeVoice = async (ctx, destination, options) => {
2787
2817
  const pan = e.pan ?? 0;
2788
2818
  const durationMs = Math.max(60, e.duration * 1e3);
2789
2819
  void renderInto(alias, e.pitch, durationMs).then((r) => {
2790
- if (r) schedule(r, t0, peak, pan, e.reverbSend);
2820
+ if (r) schedule(r, t0, peak, pan, e.reverbSend, e.destination);
2791
2821
  });
2792
2822
  };
2793
- model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato) => {
2823
+ model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs, vibrato, expr) => {
2794
2824
  if (syllable.consonant === "Q" || syllable.vowel === "") return null;
2795
2825
  const alias = resolveKoeAlias(
2796
2826
  backend.hasAlias,
@@ -2802,12 +2832,12 @@ var createKoeVoice = async (ctx, destination, options) => {
2802
2832
  if (!alias) return null;
2803
2833
  const dMs = Math.max(60, durationMs);
2804
2834
  const vib = !!vibrato && dMs / 1e3 >= VIBRATO_MIN_SEC;
2805
- const r = await renderInto(alias, pitch, dMs, vib);
2806
- return r ? keyOf(alias, pitch, dMs, vib) : null;
2835
+ const r = await renderInto(alias, pitch, dMs, vib, expr);
2836
+ return r ? keyOf(alias, pitch, dMs, vib, expr) : null;
2807
2837
  };
2808
- model.scheduleCached = (key, t0, peak, pan, reverbSend) => {
2838
+ model.scheduleCached = (key, t0, peak, pan, reverbSend, dest) => {
2809
2839
  const r = renderCache.get(key);
2810
- if (r) schedule(r, t0, peak, pan, reverbSend);
2840
+ if (r) schedule(r, t0, peak, pan, reverbSend, dest);
2811
2841
  };
2812
2842
  model.stopAll = () => {
2813
2843
  for (const src of active) {
@@ -2901,10 +2931,14 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2901
2931
  const m = loaded.get(track.model.toLowerCase());
2902
2932
  if (!m?.renderToCache) continue;
2903
2933
  let n = 0;
2934
+ const expr = {
2935
+ gender: track.gender,
2936
+ breathiness: track.breathiness
2937
+ };
2904
2938
  forEachSungNote(track, (note, prevVowel) => {
2905
2939
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2906
2940
  n++;
2907
- tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato });
2941
+ tasks.push({ model: m, note, prevVowel, vibrato: track.vibrato, expr });
2908
2942
  });
2909
2943
  }
2910
2944
  const total = tasks.length;
@@ -2920,7 +2954,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2920
2954
  task.prevVowel,
2921
2955
  task.note.pitch,
2922
2956
  task.note.durationSec * 1e3,
2923
- task.vibrato
2957
+ task.vibrato,
2958
+ task.expr
2924
2959
  ) ?? Promise.resolve(null));
2925
2960
  done++;
2926
2961
  onProgress?.(done, total);
@@ -2966,13 +3001,21 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2966
3001
  prevVowel,
2967
3002
  note.pitch,
2968
3003
  note.durationSec * 1e3,
2969
- track.vibrato
3004
+ track.vibrato,
3005
+ { gender: track.gender, breathiness: track.breathiness }
2970
3006
  );
2971
3007
  if (session !== streamSession) return;
2972
3008
  if (key) {
2973
3009
  const delay = ctx.currentTime - t0;
2974
3010
  if (delay < 0.05) {
2975
- scheduleCached(key, t0, peak, track.pan, track.reverbSend);
3011
+ scheduleCached(
3012
+ key,
3013
+ t0,
3014
+ peak,
3015
+ track.pan,
3016
+ track.reverbSend,
3017
+ options.getTrackDestination?.(track.id ?? "")
3018
+ );
2976
3019
  opts?.onScheduled?.(track, note, t0);
2977
3020
  } else {
2978
3021
  console.warn(
@@ -2985,14 +3028,15 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2985
3028
  } else {
2986
3029
  const when = t0 - ctx.currentTime;
2987
3030
  model(note.syllable, {
2988
- trackId: "",
3031
+ trackId: track.id ?? "",
2989
3032
  pitch: note.pitch,
2990
3033
  velocity: 100,
2991
3034
  volume: peak,
2992
3035
  when,
2993
3036
  duration: note.durationSec,
2994
3037
  pan: track.pan,
2995
- reverbSend: track.reverbSend
3038
+ reverbSend: track.reverbSend,
3039
+ destination: options.getTrackDestination?.(track.id ?? "")
2996
3040
  });
2997
3041
  opts?.onScheduled?.(track, note, t0);
2998
3042
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -3051,8 +3095,10 @@ var PITCH_MAP = {
3051
3095
  b: 11
3052
3096
  };
3053
3097
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
3054
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|mode)=([\w-]+)/gi;
3098
+ var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|mode)=([\w-]+)/gi;
3055
3099
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
3100
+ var TRACK_COMP_DIRECTIVE = /#t(\d+)comp=(\d+)/gi;
3101
+ var TRACK_WIDTH_DIRECTIVE = /#t(\d+)width=(\d+)/gi;
3056
3102
  var parseMmlMeta = (mml) => {
3057
3103
  const meta = {};
3058
3104
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -3066,6 +3112,9 @@ var parseMmlMeta = (mml) => {
3066
3112
  } else if (key === "drumvolume") {
3067
3113
  const dv = Number.parseInt(m[2], 10);
3068
3114
  if (!Number.isNaN(dv)) meta.drumVolume = dv;
3115
+ } else if (key === "reverb") {
3116
+ const rv = Number.parseInt(m[2], 10);
3117
+ if (!Number.isNaN(rv)) meta.reverb = clamp2(rv, 0, 100);
3069
3118
  } else if (key === "mode") {
3070
3119
  if (m[2] === "simple" || m[2] === "advanced") {
3071
3120
  meta.mode = m[2];
@@ -3080,9 +3129,25 @@ var parseMmlMeta = (mml) => {
3080
3129
  meta.trackInstruments[idx] = name;
3081
3130
  }
3082
3131
  }
3132
+ for (const m of mml.matchAll(TRACK_COMP_DIRECTIVE)) {
3133
+ const idx = Number.parseInt(m[1], 10);
3134
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 100);
3135
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3136
+ meta.trackCompression ??= {};
3137
+ meta.trackCompression[idx] = val;
3138
+ }
3139
+ }
3140
+ for (const m of mml.matchAll(TRACK_WIDTH_DIRECTIVE)) {
3141
+ const idx = Number.parseInt(m[1], 10);
3142
+ const val = clamp2(Number.parseInt(m[2], 10), 0, 200);
3143
+ if (!Number.isNaN(idx) && !Number.isNaN(val)) {
3144
+ meta.trackWidth ??= {};
3145
+ meta.trackWidth[idx] = val;
3146
+ }
3147
+ }
3083
3148
  return meta;
3084
3149
  };
3085
- var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "");
3150
+ var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "").replace(TRACK_INST_DIRECTIVE, "").replace(TRACK_COMP_DIRECTIVE, "").replace(TRACK_WIDTH_DIRECTIVE, "");
3086
3151
  var formatMmlMeta = (meta, space = "") => {
3087
3152
  const parts = [];
3088
3153
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
@@ -3091,12 +3156,24 @@ var formatMmlMeta = (meta, space = "") => {
3091
3156
  if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
3092
3157
  if (meta.drumVolume !== void 0)
3093
3158
  parts.push(`#drumvolume=${meta.drumVolume}`);
3159
+ if (meta.reverb !== void 0 && meta.reverb !== 0)
3160
+ parts.push(`#reverb=${meta.reverb}`);
3094
3161
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
3095
3162
  if (meta.trackInstruments) {
3096
3163
  for (const [idx, name] of Object.entries(meta.trackInstruments)) {
3097
3164
  if (name) parts.push(`#t${idx}inst=${name}`);
3098
3165
  }
3099
3166
  }
3167
+ if (meta.trackCompression) {
3168
+ for (const [idx, val] of Object.entries(meta.trackCompression)) {
3169
+ if (val !== 0) parts.push(`#t${idx}comp=${val}`);
3170
+ }
3171
+ }
3172
+ if (meta.trackWidth) {
3173
+ for (const [idx, val] of Object.entries(meta.trackWidth)) {
3174
+ if (val !== 100) parts.push(`#t${idx}width=${val}`);
3175
+ }
3176
+ }
3100
3177
  return parts.join(space);
3101
3178
  };
3102
3179
  var parseMML = (mml, options = {}) => {
@@ -6838,6 +6915,23 @@ var DAW_CSS = `
6838
6915
  @keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
6839
6916
  .dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
6840
6917
 
6918
+ /* \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 */
6919
+ .dtm-clip-badge {
6920
+ flex: 0 0 auto;
6921
+ min-height: 24px;
6922
+ padding: 0 8px;
6923
+ border: 2px solid var(--c-black);
6924
+ background: var(--dtm-danger);
6925
+ color: var(--c-white);
6926
+ font-family: var(--dtm-font);
6927
+ font-size: 10px;
6928
+ letter-spacing: .1em;
6929
+ cursor: pointer;
6930
+ box-shadow: 0 0 0 2px var(--dtm-danger), 2px 2px 0 var(--c-black);
6931
+ animation: dtm-blink .5s steps(1) infinite;
6932
+ }
6933
+ .dtm-clip-badge:active { transform: translate(2px,2px); box-shadow: none; }
6934
+
6841
6935
  /* \u2500\u2500\u2500 \u30A4\u30F3\u30D5\u30A9\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */
6842
6936
  .dtm-infobtn {
6843
6937
  display: inline-flex;
@@ -7113,6 +7207,37 @@ var DAW_CSS = `
7113
7207
  object-fit: cover;
7114
7208
  image-rendering: pixelated;
7115
7209
  }
7210
+ /* \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/
7211
+ \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
7212
+ \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 */
7213
+ .dtm-advanced {
7214
+ border-top: 1px dashed var(--dtm-border2);
7215
+ padding-top: 6px;
7216
+ display: flex;
7217
+ flex-direction: column;
7218
+ gap: 6px;
7219
+ }
7220
+ .dtm-advanced > summary {
7221
+ list-style: none;
7222
+ cursor: pointer;
7223
+ font-size: 11px;
7224
+ letter-spacing: .08em;
7225
+ color: var(--dtm-text);
7226
+ display: flex;
7227
+ align-items: center;
7228
+ gap: 6px;
7229
+ min-height: 28px;
7230
+ padding: 0 8px;
7231
+ border: 2px solid var(--dtm-border2);
7232
+ background: var(--dtm-deep);
7233
+ box-shadow: 2px 2px 0 var(--c-black);
7234
+ width: fit-content;
7235
+ }
7236
+ .dtm-advanced > summary:active { transform: translate(2px,2px); box-shadow: none; }
7237
+ .dtm-advanced > summary::-webkit-details-marker { display: none; }
7238
+ .dtm-advanced > summary::before { content: "\u25B6"; font-size: 9px; color: var(--dtm-accent); }
7239
+ .dtm-advanced[open] > summary::before { content: "\u25BC"; }
7240
+ .dtm-advanced[open] > summary { border-color: var(--dtm-primary); color: var(--dtm-primary); }
7116
7241
 
7117
7242
  /* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */
7118
7243
  @media (min-width: 768px) {
@@ -8884,6 +9009,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8884
9009
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
8885
9010
  vibrato: lt.vibrato,
8886
9011
  reverbSend: (lt.reverb ?? 0) / 100,
9012
+ gender: (lt.gender ?? 50) / 100,
9013
+ breathiness: (lt.breathiness ?? 50) / 100,
8887
9014
  notes
8888
9015
  };
8889
9016
  });
@@ -10585,6 +10712,7 @@ var buildUI = (target, options) => {
10585
10712
  <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
10586
10713
  <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
10587
10714
  <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
10715
+ <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>
10588
10716
  <span class="dtm-grow"></span>
10589
10717
  <span class="dtm-label">BPM</span>
10590
10718
  <input type="number" class="dtm-input dtm-input--num" data-dtm="bpm" value="${defaultBpm}" min="20" max="300">
@@ -10622,18 +10750,8 @@ var buildUI = (target, options) => {
10622
10750
  <div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
10623
10751
 
10624
10752
  <details class="dtm-panel" open>
10625
- <summary>\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10753
+ <summary>\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10626
10754
  <div class="dtm-panel-body">
10627
- <div class="dtm-row">
10628
- <span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
10629
- <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10630
- <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10631
- </div>
10632
- <div class="dtm-row">
10633
- <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6</span>
10634
- <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">
10635
- <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10636
- </div>
10637
10755
  <div class="dtm-track-body" data-dtm="track-body"></div>
10638
10756
  </div>
10639
10757
  </details>
@@ -10666,6 +10784,29 @@ var buildUI = (target, options) => {
10666
10784
  </div>
10667
10785
  </details>
10668
10786
 
10787
+ <details class="dtm-panel" open>
10788
+ <summary>\u5168\u4F53\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
10789
+ <div class="dtm-panel-body">
10790
+ <div data-dtm="preset-select-slot"></div>
10791
+ <div class="dtm-row">
10792
+ <span class="dtm-label">\u5168\u4F53\u97F3\u91CF</span>
10793
+ <input type="range" class="dtm-range dtm-grow" data-dtm="master-volume" value="50" min="0" max="100">
10794
+ <span class="dtm-label" data-dtm="master-volume-label">50%</span>
10795
+ </div>
10796
+ <div class="dtm-row">
10797
+ <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>
10798
+ <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>
10799
+ <span class="dtm-grow"></span>
10800
+ </div>
10801
+ <div class="dtm-row">
10802
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6</span>
10803
+ <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">
10804
+ <span class="dtm-label" data-dtm="reverb-amount-label">0%</span>
10805
+ <button class="dtm-infobtn" data-dtm="reverb-amount-info" title="\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
10806
+ </div>
10807
+ </div>
10808
+ </details>
10809
+
10669
10810
  <details class="dtm-panel">
10670
10811
  <summary>\u30C9\u30E9\u30E0\u8A2D\u5B9A</summary>
10671
10812
  <div class="dtm-panel-body">
@@ -10831,6 +10972,7 @@ var buildUI = (target, options) => {
10831
10972
  prevBarBtn: sel("prev-bar"),
10832
10973
  nextBarBtn: sel("next-bar"),
10833
10974
  soloCheckbox: sel("solo"),
10975
+ clipBadge: sel("clip-badge"),
10834
10976
  toolPen: sel("tool-pen"),
10835
10977
  toolSelect: sel("tool-select"),
10836
10978
  toolEraser: sel("tool-eraser"),
@@ -10859,6 +11001,9 @@ var buildUI = (target, options) => {
10859
11001
  masterVolumeLabel: sel("master-volume-label"),
10860
11002
  reverbAmount: sel("reverb-amount"),
10861
11003
  reverbAmountLabel: sel("reverb-amount-label"),
11004
+ reverbAmountInfoBtn: sel("reverb-amount-info"),
11005
+ autoMasterBtn: sel("auto-master"),
11006
+ autoMasterInfoBtn: sel("auto-master-info"),
10862
11007
  trackTabs: sel("track-tabs"),
10863
11008
  trackBody: sel("track-body"),
10864
11009
  drumSelect: sel("drum-select"),
@@ -12523,10 +12668,85 @@ var CHORD_INFO_HTML2 = `
12523
12668
  `;
12524
12669
  var VIBRATO_INFO_HTML = `
12525
12670
  <div class="dtm-modal-body-content">
12526
- <h4>\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u3068\u306F</h4>
12671
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12527
12672
  <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>
12528
- <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>
12529
- <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>
12673
+ <h4>\u77ED\u3044\u97F3\u7B26\u306B\u639B\u304B\u3089\u306A\u3044\u7406\u7531</h4>
12674
+ <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>
12675
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12676
+ <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>
12677
+ </div>
12678
+ `;
12679
+ var GENDER_INFO_HTML = `
12680
+ <div class="dtm-modal-body-content">
12681
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12682
+ <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>
12683
+ <h4>\u30AA\u30AF\u30BF\u30FC\u30D6\u30B7\u30D5\u30C8\u3068\u306E\u9055\u3044</h4>
12684
+ <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>
12685
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12686
+ <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>
12687
+ </div>
12688
+ `;
12689
+ var BREATHINESS_INFO_HTML = `
12690
+ <div class="dtm-modal-body-content">
12691
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12692
+ <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>
12693
+ <h4>\u4E0A\u3052\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12694
+ <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>
12695
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12696
+ <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>
12697
+ </div>
12698
+ `;
12699
+ var LYRIC_REVERB_INFO_HTML = `
12700
+ <div class="dtm-modal-body-content">
12701
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12702
+ <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>
12703
+ <h4>\u30DE\u30B9\u30BF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u300D\u3064\u307E\u307F\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12704
+ <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>
12705
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12706
+ <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>
12707
+ </div>
12708
+ `;
12709
+ var MASTER_REVERB_INFO_HTML = `
12710
+ <div class="dtm-modal-body-content">
12711
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12712
+ <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>
12713
+ <h4>\u5404\u30C8\u30E9\u30C3\u30AF\u306E\u300C\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u300D\u3068\u306E\u95A2\u4FC2\uFF08\u91CD\u8981\uFF09</h4>
12714
+ <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>
12715
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12716
+ <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>
12717
+ </div>
12718
+ `;
12719
+ var TRACK_COMPRESSION_INFO_HTML = `
12720
+ <div class="dtm-modal-body-content">
12721
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12722
+ <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>
12723
+ <h4>\u639B\u3051\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12724
+ <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>
12725
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
12726
+ <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>
12727
+ </div>
12728
+ `;
12729
+ var TRACK_WIDTH_INFO_HTML = `
12730
+ <div class="dtm-modal-body-content">
12731
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
12732
+ <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>
12733
+ <h4>\u300C\u5B9A\u4F4D\uFF08\u30D1\u30F3\uFF09\u300D\u3068\u306E\u9055\u3044</h4>
12734
+ <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>
12735
+ <h4>\u5E83\u3052\u3059\u304E\u308B\u3068\u3069\u3046\u306A\u308B\u304B</h4>
12736
+ <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>
12737
+ </div>
12738
+ `;
12739
+ var AUTO_MASTER_INFO_HTML = `
12740
+ <div class="dtm-modal-body-content">
12741
+ <h4>\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u3068\u306F</h4>
12742
+ <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>
12743
+ <ul>
12744
+ <li>\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6: 20%</li>
12745
+ <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u97F3\u5727\u5F37\u5316: 35%</li>
12746
+ <li>\u5168\u30C8\u30E9\u30C3\u30AF\u306E\u30B9\u30C6\u30EC\u30AA\u5E45: 115%\uFF08\u308F\u305A\u304B\u306B\u5E83\u3052\u308B\uFF09</li>
12747
+ <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>
12748
+ </ul>
12749
+ <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>
12530
12750
  </div>
12531
12751
  `;
12532
12752
  var MIDI_INFO_HTML = `
@@ -12818,6 +13038,7 @@ var mountDAW = (target, options = {}) => {
12818
13038
  let masterVolume = options.masterVolume ?? 50;
12819
13039
  options.singingVoices?.setVolume(masterVolume / 100);
12820
13040
  let reverbAmount = options.reverbAmount ?? 0;
13041
+ let unsubscribeClip;
12821
13042
  options.onReverbChange?.(reverbAmount);
12822
13043
  let drumVolume = options.drumVolume ?? 80;
12823
13044
  let currentDrumPattern = refs.drumSelect.value;
@@ -12898,7 +13119,9 @@ var mountDAW = (target, options = {}) => {
12898
13119
  vocalPan: t.vocalPan,
12899
13120
  vocalOctave: t.vocalOctave,
12900
13121
  vocalVibrato: t.vocalVibrato,
12901
- vocalReverb: t.vocalReverb
13122
+ vocalReverb: t.vocalReverb,
13123
+ vocalGender: t.vocalGender,
13124
+ vocalBreathiness: t.vocalBreathiness
12902
13125
  };
12903
13126
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
12904
13127
  lyricsDebounceTimer = setTimeout(() => {
@@ -12960,7 +13183,11 @@ var mountDAW = (target, options = {}) => {
12960
13183
  vocalOctave: 0,
12961
13184
  vocalVibrato: false,
12962
13185
  vocalReverb: 0,
12963
- trackInstrument: ""
13186
+ vocalGender: 50,
13187
+ vocalBreathiness: 50,
13188
+ trackInstrument: "",
13189
+ trackCompression: 0,
13190
+ trackWidth: 100
12964
13191
  };
12965
13192
  });
12966
13193
  };
@@ -12981,6 +13208,8 @@ var mountDAW = (target, options = {}) => {
12981
13208
  octave: t.vocalOctave,
12982
13209
  vibrato: t.vocalVibrato,
12983
13210
  reverb: t.vocalReverb,
13211
+ gender: t.vocalGender,
13212
+ breathiness: t.vocalBreathiness,
12984
13213
  syllables
12985
13214
  });
12986
13215
  });
@@ -13785,6 +14014,8 @@ var mountDAW = (target, options = {}) => {
13785
14014
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
13786
14015
  vibrato: lt.vibrato,
13787
14016
  reverbSend: (lt.reverb ?? 0) / 100,
14017
+ gender: (lt.gender ?? 50) / 100,
14018
+ breathiness: (lt.breathiness ?? 50) / 100,
13788
14019
  notes
13789
14020
  };
13790
14021
  }) : [];
@@ -13890,7 +14121,22 @@ var mountDAW = (target, options = {}) => {
13890
14121
  <span class="dtm-label">\u30D9\u30ED\u30B7\u30C6\u30A3</span>
13891
14122
  <input type="range" class="dtm-range dtm-grow" data-dtm="track-vol" min="0" max="127" value="${active.volume}">
13892
14123
  <span class="dtm-label" data-dtm="track-vol-label">${active.volume}</span>
13893
- </div>`;
14124
+ </div>
14125
+ <details class="dtm-advanced" data-dtm="track-fx-advanced">
14126
+ <summary>\u8A73\u7D30\u8A2D\u5B9A\uFF08\u97F3\u5727\u30FB\u30B9\u30C6\u30EC\u30AA\u5E45\uFF09</summary>
14127
+ <div class="dtm-row">
14128
+ <span class="dtm-label">\u97F3\u5727\u5F37\u5316</span>
14129
+ <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">
14130
+ <span class="dtm-label" data-dtm="track-comp-label"></span>
14131
+ <button class="dtm-infobtn" data-dtm="track-comp-info" title="\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14132
+ </div>
14133
+ <div class="dtm-row">
14134
+ <span class="dtm-label">\u30B9\u30C6\u30EC\u30AA\u5E45</span>
14135
+ <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">
14136
+ <span class="dtm-label" data-dtm="track-width-label"></span>
14137
+ <button class="dtm-infobtn" data-dtm="track-width-info" title="\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14138
+ </div>
14139
+ </details>`;
13894
14140
  const volInput = refs.trackBody.querySelector(
13895
14141
  '[data-dtm="track-vol"]'
13896
14142
  );
@@ -13907,6 +14153,47 @@ var mountDAW = (target, options = {}) => {
13907
14153
  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" : "";
13908
14154
  };
13909
14155
  syncVelocityDisabled();
14156
+ const trackCompInput = refs.trackBody.querySelector(
14157
+ '[data-dtm="track-comp"]'
14158
+ );
14159
+ const trackCompLabel = refs.trackBody.querySelector(
14160
+ '[data-dtm="track-comp-label"]'
14161
+ );
14162
+ const trackWidthInput = refs.trackBody.querySelector(
14163
+ '[data-dtm="track-width"]'
14164
+ );
14165
+ const trackWidthLabel = refs.trackBody.querySelector(
14166
+ '[data-dtm="track-width-label"]'
14167
+ );
14168
+ const trackCompInfo = refs.trackBody.querySelector(
14169
+ '[data-dtm="track-comp-info"]'
14170
+ );
14171
+ const trackWidthInfo = refs.trackBody.querySelector(
14172
+ '[data-dtm="track-width-info"]'
14173
+ );
14174
+ trackCompInput.value = String(active.trackCompression);
14175
+ trackCompLabel.textContent = `${active.trackCompression}%`;
14176
+ trackWidthInput.value = String(active.trackWidth);
14177
+ trackWidthLabel.textContent = `${active.trackWidth}%`;
14178
+ trackCompInput.addEventListener("input", () => {
14179
+ active.trackCompression = Number.parseInt(trackCompInput.value, 10);
14180
+ trackCompLabel.textContent = `${active.trackCompression}%`;
14181
+ options.onTrackCompressionChange?.(
14182
+ active.config.id,
14183
+ active.trackCompression
14184
+ );
14185
+ });
14186
+ trackWidthInput.addEventListener("input", () => {
14187
+ active.trackWidth = Number.parseInt(trackWidthInput.value, 10);
14188
+ trackWidthLabel.textContent = `${active.trackWidth}%`;
14189
+ options.onTrackWidthChange?.(active.config.id, active.trackWidth);
14190
+ });
14191
+ trackCompInfo.addEventListener("click", () => {
14192
+ showModal("\u97F3\u5727\u5F37\u5316\u306E\u89E3\u8AAC", TRACK_COMPRESSION_INFO_HTML);
14193
+ });
14194
+ trackWidthInfo.addEventListener("click", () => {
14195
+ showModal("\u30B9\u30C6\u30EC\u30AA\u5E45\u306E\u89E3\u8AAC", TRACK_WIDTH_INFO_HTML);
14196
+ });
13910
14197
  const instRow = document.createElement("div");
13911
14198
  instRow.className = "dtm-row";
13912
14199
  instRow.innerHTML = `<span class="dtm-label">\u697D\u5668</span>`;
@@ -13970,17 +14257,6 @@ var mountDAW = (target, options = {}) => {
13970
14257
  <span class="dtm-label">\u266A UTAU</span>
13971
14258
  <select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
13972
14259
  <img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
13973
- <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">
13974
- <option value="2">+2 oct</option>
13975
- <option value="1">+1 oct</option>
13976
- <option value="0">\xB10 oct</option>
13977
- <option value="-1">-1 oct</option>
13978
- <option value="-2">-2 oct</option>
13979
- </select>
13980
- <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">
13981
- <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
13982
- </label>
13983
- <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
13984
14260
  <span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
13985
14261
  </div>
13986
14262
  <div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
@@ -14006,16 +14282,48 @@ var mountDAW = (target, options = {}) => {
14006
14282
  <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">
14007
14283
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
14008
14284
  </div>
14009
- <div class="dtm-row">
14010
- <span class="dtm-label">\u5B9A\u4F4D</span>
14011
- <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">
14012
- <span class="dtm-label" data-dtm="lyric-pan-label"></span>
14013
- </div>
14014
- <div class="dtm-row">
14015
- <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14016
- <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">
14017
- <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14018
- </div>
14285
+ <details class="dtm-advanced" data-dtm="lyric-advanced">
14286
+ <summary>\u8A73\u7D30\u8A2D\u5B9A</summary>
14287
+ <div class="dtm-row">
14288
+ <span class="dtm-label">\u30AA\u30AF\u30BF\u30FC\u30D6</span>
14289
+ <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">
14290
+ <option value="2">+2 oct</option>
14291
+ <option value="1">+1 oct</option>
14292
+ <option value="0">\xB10 oct</option>
14293
+ <option value="-1">-1 oct</option>
14294
+ <option value="-2">-2 oct</option>
14295
+ </select>
14296
+ </div>
14297
+ <div class="dtm-row">
14298
+ <span class="dtm-label">\u5B9A\u4F4D</span>
14299
+ <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">
14300
+ <span class="dtm-label" data-dtm="lyric-pan-label"></span>
14301
+ </div>
14302
+ <div class="dtm-row">
14303
+ <span class="dtm-label">\u30EA\u30D0\u30FC\u30D6\u9001\u308A</span>
14304
+ <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">
14305
+ <span class="dtm-label" data-dtm="lyric-reverb-label"></span>
14306
+ <button class="dtm-infobtn" data-dtm="lyric-reverb-info" title="\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14307
+ </div>
14308
+ <div class="dtm-row">
14309
+ <span class="dtm-label">\u30B8\u30A7\u30F3\u30C0\u30FC</span>
14310
+ <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">
14311
+ <span class="dtm-label" data-dtm="lyric-gender-label"></span>
14312
+ <button class="dtm-infobtn" data-dtm="lyric-gender-info" title="\u30B8\u30A7\u30F3\u30C0\u30FC\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14313
+ </div>
14314
+ <div class="dtm-row">
14315
+ <span class="dtm-label">\u30D6\u30EC\u30B7\u30CD\u30B9</span>
14316
+ <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">
14317
+ <span class="dtm-label" data-dtm="lyric-breathiness-label"></span>
14318
+ <button class="dtm-infobtn" data-dtm="lyric-breathiness-info" title="\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14319
+ </div>
14320
+ <div class="dtm-row">
14321
+ <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
14322
+ <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
14323
+ </span>
14324
+ <button class="dtm-infobtn" data-dtm="lyric-vibrato-info" title="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
14325
+ </div>
14326
+ </details>
14019
14327
  <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>
14020
14328
  </div>`;
14021
14329
  refs.trackBody.appendChild(lyricDiv);
@@ -14055,6 +14363,36 @@ var mountDAW = (target, options = {}) => {
14055
14363
  const lyricReverbLabel = lyricDiv.querySelector(
14056
14364
  '[data-dtm="lyric-reverb-label"]'
14057
14365
  );
14366
+ const lyricReverbInfo = lyricDiv.querySelector(
14367
+ '[data-dtm="lyric-reverb-info"]'
14368
+ );
14369
+ lyricReverbInfo.addEventListener("click", () => {
14370
+ showModal("\u30EA\u30D0\u30FC\u30D6\u9001\u308A\u306E\u89E3\u8AAC", LYRIC_REVERB_INFO_HTML);
14371
+ });
14372
+ const lyricGender = lyricDiv.querySelector(
14373
+ '[data-dtm="lyric-gender"]'
14374
+ );
14375
+ const lyricGenderLabel = lyricDiv.querySelector(
14376
+ '[data-dtm="lyric-gender-label"]'
14377
+ );
14378
+ const lyricGenderInfo = lyricDiv.querySelector(
14379
+ '[data-dtm="lyric-gender-info"]'
14380
+ );
14381
+ lyricGenderInfo.addEventListener("click", () => {
14382
+ showModal("\u30B8\u30A7\u30F3\u30C0\u30FC\u306E\u89E3\u8AAC", GENDER_INFO_HTML);
14383
+ });
14384
+ const lyricBreathiness = lyricDiv.querySelector(
14385
+ '[data-dtm="lyric-breathiness"]'
14386
+ );
14387
+ const lyricBreathinessLabel = lyricDiv.querySelector(
14388
+ '[data-dtm="lyric-breathiness-label"]'
14389
+ );
14390
+ const lyricBreathinessInfo = lyricDiv.querySelector(
14391
+ '[data-dtm="lyric-breathiness-info"]'
14392
+ );
14393
+ lyricBreathinessInfo.addEventListener("click", () => {
14394
+ showModal("\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC", BREATHINESS_INFO_HTML);
14395
+ });
14058
14396
  const lyricVibrato = lyricDiv.querySelector(
14059
14397
  '[data-dtm="lyric-vibrato"]'
14060
14398
  );
@@ -14127,6 +14465,10 @@ var mountDAW = (target, options = {}) => {
14127
14465
  lyricPanLabel.textContent = fmtPan(active.vocalPan);
14128
14466
  lyricReverb.value = String(active.vocalReverb);
14129
14467
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14468
+ lyricGender.value = String(active.vocalGender);
14469
+ lyricGenderLabel.textContent = `${active.vocalGender}`;
14470
+ lyricBreathiness.value = String(active.vocalBreathiness);
14471
+ lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14130
14472
  lyricVibrato.checked = active.vocalVibrato;
14131
14473
  const updateLyricCount = () => {
14132
14474
  const n = normalizeLyrics(lyricInput.value).length;
@@ -14177,7 +14519,6 @@ var mountDAW = (target, options = {}) => {
14177
14519
  };
14178
14520
  const syncLyricVisibility = () => {
14179
14521
  lyricBody.style.display = active.lyricModel ? "" : "none";
14180
- lyricOctaveSel.style.display = active.lyricModel ? "" : "none";
14181
14522
  updateLyricCount();
14182
14523
  syncLyricTerms();
14183
14524
  syncLyricIcon();
@@ -14278,6 +14619,16 @@ var mountDAW = (target, options = {}) => {
14278
14619
  lyricReverbLabel.textContent = `${active.vocalReverb}%`;
14279
14620
  fireLyricsChange(active);
14280
14621
  });
14622
+ lyricGender.addEventListener("input", () => {
14623
+ active.vocalGender = Number.parseInt(lyricGender.value, 10);
14624
+ lyricGenderLabel.textContent = `${active.vocalGender}`;
14625
+ fireLyricsChange(active);
14626
+ });
14627
+ lyricBreathiness.addEventListener("input", () => {
14628
+ active.vocalBreathiness = Number.parseInt(lyricBreathiness.value, 10);
14629
+ lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
14630
+ fireLyricsChange(active);
14631
+ });
14281
14632
  }
14282
14633
  if (active.config.id === "chord" && showChord) {
14283
14634
  const div = document.createElement("div");
@@ -14361,18 +14712,28 @@ var mountDAW = (target, options = {}) => {
14361
14712
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
14362
14713
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
14363
14714
  const trackInstrumentsForMeta = {};
14715
+ const trackCompressionForMeta = {};
14716
+ const trackWidthForMeta = {};
14364
14717
  trackStates.forEach((t, i2) => {
14365
14718
  if (t.trackInstrument) trackInstrumentsForMeta[i2] = t.trackInstrument;
14719
+ if (t.trackCompression !== 0)
14720
+ trackCompressionForMeta[i2] = t.trackCompression;
14721
+ if (t.trackWidth !== 100) trackWidthForMeta[i2] = t.trackWidth;
14366
14722
  });
14367
14723
  const trackInstMeta = Object.keys(trackInstrumentsForMeta).length > 0 ? trackInstrumentsForMeta : void 0;
14724
+ const trackCompMeta = Object.keys(trackCompressionForMeta).length > 0 ? trackCompressionForMeta : void 0;
14725
+ const trackWidthMeta = Object.keys(trackWidthForMeta).length > 0 ? trackWidthForMeta : void 0;
14368
14726
  const metaLineFull = formatMmlMeta(
14369
14727
  {
14370
14728
  instrument: currentInstrument || void 0,
14371
14729
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
14372
14730
  volume: masterVolume,
14373
14731
  drumVolume,
14732
+ reverb: reverbAmount,
14374
14733
  mode,
14375
- trackInstruments: trackInstMeta
14734
+ trackInstruments: trackInstMeta,
14735
+ trackCompression: trackCompMeta,
14736
+ trackWidth: trackWidthMeta
14376
14737
  },
14377
14738
  " "
14378
14739
  );
@@ -14382,8 +14743,11 @@ var mountDAW = (target, options = {}) => {
14382
14743
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
14383
14744
  volume: masterVolume,
14384
14745
  drumVolume,
14746
+ reverb: reverbAmount,
14385
14747
  mode,
14386
- trackInstruments: trackInstMeta
14748
+ trackInstruments: trackInstMeta,
14749
+ trackCompression: trackCompMeta,
14750
+ trackWidth: trackWidthMeta
14387
14751
  },
14388
14752
  ""
14389
14753
  );
@@ -14432,7 +14796,9 @@ var mountDAW = (target, options = {}) => {
14432
14796
  pan: t.vocalPan,
14433
14797
  oct: t.vocalOctave,
14434
14798
  vib: t.vocalVibrato,
14435
- rev: t.vocalReverb
14799
+ rev: t.vocalReverb,
14800
+ gen: t.vocalGender,
14801
+ bre: t.vocalBreathiness
14436
14802
  })).filter(
14437
14803
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
14438
14804
  ).map((x2) => {
@@ -14442,7 +14808,9 @@ var mountDAW = (target, options = {}) => {
14442
14808
  x2.pan === 64 ? "" : `p${x2.pan}`,
14443
14809
  x2.oct === 0 ? "" : `o${x2.oct}`,
14444
14810
  x2.vib ? "b1" : "",
14445
- x2.rev === 0 ? "" : `r${x2.rev}`
14811
+ x2.rev === 0 ? "" : `r${x2.rev}`,
14812
+ x2.gen === 50 ? "" : `g${x2.gen}`,
14813
+ x2.bre === 50 ? "" : `h${x2.bre}`
14446
14814
  ].filter((s) => s.length > 0).join(" ");
14447
14815
  const head = params ? `${x2.model} ${params}` : x2.model;
14448
14816
  return `@@${x2.i} ${head} ${x2.text}`;
@@ -14582,6 +14950,12 @@ var mountDAW = (target, options = {}) => {
14582
14950
  refs.drumVolume.value = String(meta.drumVolume);
14583
14951
  refs.drumVolumeLabel.textContent = `${drumVolume}%`;
14584
14952
  }
14953
+ if (meta.reverb !== void 0) {
14954
+ reverbAmount = meta.reverb;
14955
+ refs.reverbAmount.value = String(meta.reverb);
14956
+ refs.reverbAmountLabel.textContent = `${meta.reverb}%`;
14957
+ options.onReverbChange?.(meta.reverb);
14958
+ }
14585
14959
  }
14586
14960
  trackStates.forEach((t, i2) => {
14587
14961
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
@@ -14591,6 +14965,19 @@ var mountDAW = (target, options = {}) => {
14591
14965
  options.onTrackInstrumentChange?.(i2, name);
14592
14966
  }
14593
14967
  });
14968
+ trackStates.forEach((t, i2) => {
14969
+ if (applyActiveOnly && i2 !== activeTrackIndex) return;
14970
+ const comp = meta.trackCompression?.[i2] ?? 0;
14971
+ if (t.trackCompression !== comp) {
14972
+ t.trackCompression = comp;
14973
+ options.onTrackCompressionChange?.(t.config.id, comp);
14974
+ }
14975
+ const width = meta.trackWidth?.[i2] ?? 100;
14976
+ if (t.trackWidth !== width) {
14977
+ t.trackWidth = width;
14978
+ options.onTrackWidthChange?.(t.config.id, width);
14979
+ }
14980
+ });
14594
14981
  trackStates.forEach((t, i2) => {
14595
14982
  if (applyActiveOnly && i2 !== activeTrackIndex) return;
14596
14983
  const v = trackVelocity.get(i2);
@@ -14609,6 +14996,8 @@ var mountDAW = (target, options = {}) => {
14609
14996
  active.vocalOctave = 0;
14610
14997
  active.vocalVibrato = false;
14611
14998
  active.vocalReverb = 0;
14999
+ active.vocalGender = 50;
15000
+ active.vocalBreathiness = 50;
14612
15001
  } else {
14613
15002
  for (const t of trackStates) {
14614
15003
  t.lyrics = "";
@@ -14619,6 +15008,8 @@ var mountDAW = (target, options = {}) => {
14619
15008
  t.vocalOctave = 0;
14620
15009
  t.vocalVibrato = false;
14621
15010
  t.vocalReverb = 0;
15011
+ t.vocalGender = 50;
15012
+ t.vocalBreathiness = 50;
14622
15013
  }
14623
15014
  }
14624
15015
  lyrics?.forEach((lt) => {
@@ -14633,6 +15024,8 @@ var mountDAW = (target, options = {}) => {
14633
15024
  t.vocalOctave = lt.octave ?? 0;
14634
15025
  t.vocalVibrato = lt.vibrato ?? false;
14635
15026
  t.vocalReverb = lt.reverb ?? 0;
15027
+ t.vocalGender = lt.gender ?? 50;
15028
+ t.vocalBreathiness = lt.breathiness ?? 50;
14636
15029
  });
14637
15030
  for (const p of placements) {
14638
15031
  if (applyActiveOnly && p.trackIndex !== activeTrackIndex) continue;
@@ -14713,6 +15106,8 @@ var mountDAW = (target, options = {}) => {
14713
15106
  active.vocalOctave = 0;
14714
15107
  active.vocalVibrato = false;
14715
15108
  active.vocalReverb = 0;
15109
+ active.vocalGender = 50;
15110
+ active.vocalBreathiness = 50;
14716
15111
  } else {
14717
15112
  clearAll();
14718
15113
  for (const t of trackStates) t.core.setLoadMode(true);
@@ -14725,6 +15120,8 @@ var mountDAW = (target, options = {}) => {
14725
15120
  t.vocalOctave = 0;
14726
15121
  t.vocalVibrato = false;
14727
15122
  t.vocalReverb = 0;
15123
+ t.vocalGender = 50;
15124
+ t.vocalBreathiness = 50;
14728
15125
  }
14729
15126
  }
14730
15127
  const { placements, bpm: parsedBpm } = isAdvanced ? extractMidiPlacementsByTrack(
@@ -14859,6 +15256,12 @@ var mountDAW = (target, options = {}) => {
14859
15256
  isSolo = refs.soloCheckbox.checked;
14860
15257
  redrawAll();
14861
15258
  });
15259
+ unsubscribeClip = options.clipMeter?.onClipChange((clipping) => {
15260
+ refs.clipBadge.classList.toggle("dtm-hidden", !clipping);
15261
+ });
15262
+ refs.clipBadge.addEventListener("click", () => {
15263
+ options.clipMeter?.reset();
15264
+ });
14862
15265
  refs.toolPen.addEventListener("click", () => setToolMode("pen"));
14863
15266
  refs.toolSelect.addEventListener("click", () => setToolMode("select"));
14864
15267
  refs.toolEraser.addEventListener("click", () => setToolMode("eraser"));
@@ -14939,6 +15342,30 @@ var mountDAW = (target, options = {}) => {
14939
15342
  refs.reverbAmountLabel.textContent = `${reverbAmount}%`;
14940
15343
  options.onReverbChange?.(reverbAmount);
14941
15344
  });
15345
+ refs.reverbAmountInfoBtn.addEventListener("click", () => {
15346
+ showModal("\u30DE\u30B9\u30BF\u30EA\u30D0\u30FC\u30D6\u306E\u89E3\u8AAC", MASTER_REVERB_INFO_HTML);
15347
+ });
15348
+ refs.autoMasterInfoBtn.addEventListener("click", () => {
15349
+ showModal("\u304A\u307E\u304B\u305B\u30DE\u30B9\u30BF\u30EA\u30F3\u30B0\u89E3\u8AAC", AUTO_MASTER_INFO_HTML);
15350
+ });
15351
+ refs.autoMasterBtn.addEventListener("click", () => {
15352
+ reverbAmount = 20;
15353
+ refs.reverbAmount.value = "20";
15354
+ refs.reverbAmountLabel.textContent = "20%";
15355
+ options.onReverbChange?.(20);
15356
+ for (const t of trackStates) {
15357
+ t.trackCompression = 35;
15358
+ t.trackWidth = 115;
15359
+ options.onTrackCompressionChange?.(t.config.id, 35);
15360
+ options.onTrackWidthChange?.(t.config.id, 115);
15361
+ if (t.lyricModel) {
15362
+ t.vocalVibrato = true;
15363
+ t.vocalReverb = 25;
15364
+ fireLyricsChange(t);
15365
+ }
15366
+ }
15367
+ updateTrackPanel();
15368
+ });
14942
15369
  refs.drumSelect.addEventListener("change", () => {
14943
15370
  currentDrumPattern = refs.drumSelect.value;
14944
15371
  options.onDrumChange?.(currentDrumPattern);
@@ -15950,6 +16377,8 @@ var mountDAW = (target, options = {}) => {
15950
16377
  t.vocalOctave = data.vocalOctave;
15951
16378
  t.vocalVibrato = data.vocalVibrato ?? false;
15952
16379
  t.vocalReverb = data.vocalReverb ?? 0;
16380
+ t.vocalGender = data.vocalGender ?? 50;
16381
+ t.vocalBreathiness = data.vocalBreathiness ?? 50;
15953
16382
  },
15954
16383
  applyTrackInstrument: (trackIndex, instrumentName) => {
15955
16384
  const t = trackStates[trackIndex];
@@ -15975,6 +16404,7 @@ var mountDAW = (target, options = {}) => {
15975
16404
  destroy: () => {
15976
16405
  sequencer.stop();
15977
16406
  options.singingVoices?.stopStream();
16407
+ unsubscribeClip?.();
15978
16408
  resizeObserver.disconnect();
15979
16409
  document.removeEventListener("pointermove", onPointerMove);
15980
16410
  document.removeEventListener("pointerup", onPointerUp);
@@ -16067,6 +16497,8 @@ var playSingingMML = async (mml, options = {}) => {
16067
16497
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
16068
16498
  vibrato: lt.vibrato,
16069
16499
  reverbSend: (lt.reverb ?? 0) / 100,
16500
+ gender: (lt.gender ?? 50) / 100,
16501
+ breathiness: (lt.breathiness ?? 50) / 100,
16070
16502
  notes
16071
16503
  };
16072
16504
  });
@@ -16674,6 +17106,147 @@ var isSupported = midiJsonParser.isSupported;
16674
17106
  var parseArrayBuffer = midiJsonParser.parseArrayBuffer;
16675
17107
  URL.revokeObjectURL(url);
16676
17108
 
17109
+ // src/channel-strip.ts
17110
+ var clamp4 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
17111
+ var compressionParams = (amount) => {
17112
+ const t = clamp4(amount, 0, 100) / 100;
17113
+ return {
17114
+ threshold: 0 + (-24 - 0) * t,
17115
+ ratio: 1 + (12 - 1) * t,
17116
+ knee: 0 + (6 - 0) * t,
17117
+ attack: 0.02 + (3e-3 - 0.02) * t,
17118
+ release: 0.25 + (0.15 - 0.25) * t
17119
+ };
17120
+ };
17121
+ var createChannelStrip = (ctx, destination, options = {}) => {
17122
+ const input = ctx.createGain();
17123
+ const compressor = ctx.createDynamicsCompressor();
17124
+ const applyCompression = (amount) => {
17125
+ const p = compressionParams(amount);
17126
+ const now = ctx.currentTime;
17127
+ compressor.threshold.setValueAtTime(p.threshold, now);
17128
+ compressor.ratio.setValueAtTime(p.ratio, now);
17129
+ compressor.knee.setValueAtTime(p.knee, now);
17130
+ compressor.attack.setValueAtTime(p.attack, now);
17131
+ compressor.release.setValueAtTime(p.release, now);
17132
+ };
17133
+ applyCompression(options.compression ?? 0);
17134
+ const splitter = ctx.createChannelSplitter(2);
17135
+ const mid = ctx.createGain();
17136
+ mid.gain.value = 0.5;
17137
+ splitter.connect(mid, 0);
17138
+ splitter.connect(mid, 1);
17139
+ const sideSum = ctx.createGain();
17140
+ sideSum.gain.value = 1;
17141
+ const sideL = ctx.createGain();
17142
+ sideL.gain.value = 0.5;
17143
+ const sideR = ctx.createGain();
17144
+ sideR.gain.value = -0.5;
17145
+ splitter.connect(sideL, 0);
17146
+ splitter.connect(sideR, 1);
17147
+ sideL.connect(sideSum);
17148
+ sideR.connect(sideSum);
17149
+ const widthGain = ctx.createGain();
17150
+ sideSum.connect(widthGain);
17151
+ const widthInv = ctx.createGain();
17152
+ widthInv.gain.value = -1;
17153
+ widthGain.connect(widthInv);
17154
+ const outL = ctx.createGain();
17155
+ mid.connect(outL);
17156
+ widthGain.connect(outL);
17157
+ const outR = ctx.createGain();
17158
+ mid.connect(outR);
17159
+ widthInv.connect(outR);
17160
+ const merger = ctx.createChannelMerger(2);
17161
+ outL.connect(merger, 0, 0);
17162
+ outR.connect(merger, 0, 1);
17163
+ const setWidth = (width) => {
17164
+ widthGain.gain.setTargetAtTime(
17165
+ clamp4(width, 0, 200) / 100,
17166
+ ctx.currentTime,
17167
+ 0.02
17168
+ );
17169
+ };
17170
+ setWidth(options.width ?? 100);
17171
+ input.connect(compressor);
17172
+ compressor.connect(splitter);
17173
+ merger.connect(destination);
17174
+ return {
17175
+ input,
17176
+ setCompression: applyCompression,
17177
+ setWidth,
17178
+ dispose: () => {
17179
+ input.disconnect();
17180
+ compressor.disconnect();
17181
+ splitter.disconnect();
17182
+ mid.disconnect();
17183
+ sideSum.disconnect();
17184
+ sideL.disconnect();
17185
+ sideR.disconnect();
17186
+ widthGain.disconnect();
17187
+ widthInv.disconnect();
17188
+ outL.disconnect();
17189
+ outR.disconnect();
17190
+ merger.disconnect();
17191
+ }
17192
+ };
17193
+ };
17194
+
17195
+ // src/clip-meter.ts
17196
+ var createClipMeter = (ctx, source, options = {}) => {
17197
+ const threshold = options.threshold ?? 0.98;
17198
+ const holdMs = options.holdMs ?? 800;
17199
+ const analyser = ctx.createAnalyser();
17200
+ analyser.fftSize = 512;
17201
+ source.connect(analyser);
17202
+ const buf = new Float32Array(analyser.fftSize);
17203
+ let clipping = false;
17204
+ let clipUntil = 0;
17205
+ let peakLevel = 0;
17206
+ const listeners = /* @__PURE__ */ new Set();
17207
+ let rafId = null;
17208
+ const tick = () => {
17209
+ analyser.getFloatTimeDomainData(buf);
17210
+ let peak = 0;
17211
+ let clipped = false;
17212
+ for (let i2 = 0; i2 < buf.length; i2++) {
17213
+ const v = Math.abs(buf[i2]);
17214
+ if (v > peak) peak = v;
17215
+ if (v >= threshold) clipped = true;
17216
+ }
17217
+ peakLevel = peak;
17218
+ const now = performance.now();
17219
+ if (clipped) clipUntil = now + holdMs;
17220
+ const nextClipping = now < clipUntil;
17221
+ if (nextClipping !== clipping) {
17222
+ clipping = nextClipping;
17223
+ for (const cb of listeners) cb(clipping);
17224
+ }
17225
+ rafId = requestAnimationFrame(tick);
17226
+ };
17227
+ rafId = requestAnimationFrame(tick);
17228
+ return {
17229
+ onClipChange: (cb) => {
17230
+ listeners.add(cb);
17231
+ return () => listeners.delete(cb);
17232
+ },
17233
+ getPeakLevel: () => peakLevel,
17234
+ reset: () => {
17235
+ clipUntil = 0;
17236
+ if (clipping) {
17237
+ clipping = false;
17238
+ for (const cb of listeners) cb(false);
17239
+ }
17240
+ },
17241
+ dispose: () => {
17242
+ if (rafId !== null) cancelAnimationFrame(rafId);
17243
+ rafId = null;
17244
+ analyser.disconnect();
17245
+ listeners.clear();
17246
+ }
17247
+ };
17248
+ };
17249
+
16677
17250
  // src/reverb.ts
16678
17251
  var IMPULSE_DURATION_SEC = 2.2;
16679
17252
  var IMPULSE_DECAY = 2.5;
@@ -16844,7 +17417,6 @@ var createDtmStudio = async (options = {}) => {
16844
17417
  const audioCtx = options.audioContext ?? new AudioContext({ sampleRate: 44100 });
16845
17418
  const masterGain = audioCtx.createGain();
16846
17419
  masterGain.gain.value = options.masterVolume ?? 1;
16847
- masterGain.connect(audioCtx.destination);
16848
17420
  const drumGain = audioCtx.createGain();
16849
17421
  drumGain.gain.value = options.drumVolume ?? 1;
16850
17422
  drumGain.connect(masterGain);
@@ -16855,7 +17427,6 @@ var createDtmStudio = async (options = {}) => {
16855
17427
  reverbWetGain.gain.value = reverbAmountToGain(options.reverbAmount ?? 0);
16856
17428
  masterGain.connect(reverbConvolver);
16857
17429
  reverbConvolver.connect(reverbWetGain);
16858
- reverbWetGain.connect(audioCtx.destination);
16859
17430
  const setReverbAmount = (amount) => {
16860
17431
  reverbWetGain.gain.setTargetAtTime(
16861
17432
  reverbAmountToGain(amount),
@@ -16863,6 +17434,27 @@ var createDtmStudio = async (options = {}) => {
16863
17434
  0.02
16864
17435
  );
16865
17436
  };
17437
+ const finalMix = audioCtx.createGain();
17438
+ masterGain.connect(finalMix);
17439
+ reverbWetGain.connect(finalMix);
17440
+ const safetyLimiter = audioCtx.createDynamicsCompressor();
17441
+ safetyLimiter.threshold.value = -1;
17442
+ safetyLimiter.knee.value = 0;
17443
+ safetyLimiter.ratio.value = 20;
17444
+ safetyLimiter.attack.value = 1e-3;
17445
+ safetyLimiter.release.value = 0.1;
17446
+ finalMix.connect(safetyLimiter);
17447
+ safetyLimiter.connect(options.destination ?? audioCtx.destination);
17448
+ const clipMeter = createClipMeter(audioCtx, finalMix);
17449
+ const channelStrips = /* @__PURE__ */ new Map();
17450
+ const getChannelStrip = (trackId) => {
17451
+ let strip = channelStrips.get(trackId);
17452
+ if (!strip) {
17453
+ strip = createChannelStrip(audioCtx, masterGain);
17454
+ channelStrips.set(trackId, strip);
17455
+ }
17456
+ return strip;
17457
+ };
16866
17458
  const resumeAudio = () => {
16867
17459
  if (audioCtx.state === "closed") return Promise.resolve();
16868
17460
  return audioCtx.resume();
@@ -16896,7 +17488,11 @@ var createDtmStudio = async (options = {}) => {
16896
17488
  worldlineScriptUrl: options.worldlineScriptUrl,
16897
17489
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
16898
17490
  // Convolver 入力へ直接センドする(masterGain 経由のドライ段は素通り)。
16899
- reverbBus: reverbConvolver
17491
+ reverbBus: reverbConvolver,
17492
+ // トラック単位チャンネルストリップ(コンプレッサー/ステレオワイド)の入口。
17493
+ // 楽器と同じ getChannelStrip キャッシュを共有するので、演奏トラックと歌詞トラックが
17494
+ // 同じ trackId を指していれば同じ処理が掛かる。
17495
+ getTrackDestination: (trackId) => getChannelStrip(trackId).input
16900
17496
  });
16901
17497
  const listReady = new Promise((resolve) => {
16902
17498
  sfList.init();
@@ -17126,7 +17722,7 @@ var createDtmStudio = async (options = {}) => {
17126
17722
  if (!sfInst) return;
17127
17723
  sfInst.play({
17128
17724
  ctx: audioCtx,
17129
- destination: masterGain,
17725
+ destination: getChannelStrip(e.trackId).input,
17130
17726
  pitch: e.pitch,
17131
17727
  volume: e.volume,
17132
17728
  when: e.when,
@@ -17173,6 +17769,9 @@ var createDtmStudio = async (options = {}) => {
17173
17769
  },
17174
17770
  reverbAmount: options.reverbAmount,
17175
17771
  onReverbChange: setReverbAmount,
17772
+ onTrackCompressionChange: (trackId, amount) => getChannelStrip(trackId).setCompression(amount),
17773
+ onTrackWidthChange: (trackId, width) => getChannelStrip(trackId).setWidth(width),
17774
+ clipMeter,
17176
17775
  ...dawOverrides,
17177
17776
  // dawOverrides で上書きされないよう、スプレッドの後に配置して合成する
17178
17777
  onDrumChange: (name) => {
@@ -17199,17 +17798,22 @@ var createDtmStudio = async (options = {}) => {
17199
17798
  mountedEditors.push(daw);
17200
17799
  const wantPresetUI = presetUI ?? features.presetUI;
17201
17800
  const rollEl = target.querySelector('[data-dtm="roll"]');
17801
+ const presetSlot = target.querySelector(
17802
+ '[data-dtm="preset-select-slot"]'
17803
+ );
17202
17804
  if (wantPresetUI) {
17203
17805
  editorPresetSelects.get(target)?.destroy();
17204
- presetSelect = mountPresetSelect(target, {
17806
+ presetSelect = mountPresetSelect(presetSlot ?? target, {
17205
17807
  getDaw: () => daw,
17206
17808
  getTrackIds: () => trackIds,
17207
17809
  value: initialPreset,
17208
17810
  loadingTarget: rollEl ?? target,
17209
- position: "prepend",
17210
- // 楽器変更時、このエディタの発音解決が使うプリセットも追従させる。
17811
+ position: presetSlot ? "append" : "prepend",
17812
+ // 楽器変更時、このエディタの発音解決が使うプリセットも追従させ、
17813
+ // 外部の onInstrumentChange(呼び出し側の永続化フック等)へも通知する。
17814
+ // #inst= 付きMML読込時の通知経路(handleInstrumentChange)と揃える。
17211
17815
  onChange: (key) => {
17212
- editorPreset = key;
17816
+ handleInstrumentChange(key);
17213
17817
  }
17214
17818
  });
17215
17819
  editorPresetSelects.set(target, presetSelect);
@@ -17392,7 +17996,7 @@ var createDtmStudio = async (options = {}) => {
17392
17996
  if (!sfInst) return;
17393
17997
  sfInst.play({
17394
17998
  ctx: audioCtx,
17395
- destination: masterGain,
17999
+ destination: getChannelStrip(e.trackId).input,
17396
18000
  pitch: e.pitch,
17397
18001
  volume: e.volume,
17398
18002
  when: e.when,
@@ -17478,7 +18082,7 @@ var createDtmStudio = async (options = {}) => {
17478
18082
  if (!sfInst) return;
17479
18083
  sfInst.play({
17480
18084
  ctx: audioCtx,
17481
- destination: masterGain,
18085
+ destination: getChannelStrip(e.trackId).input,
17482
18086
  pitch: e.pitch,
17483
18087
  volume: e.volume,
17484
18088
  when: e.when,
@@ -17557,7 +18161,7 @@ var createDtmStudio = async (options = {}) => {
17557
18161
  if (!sfInst) return;
17558
18162
  sfInst.play({
17559
18163
  ctx: audioCtx,
17560
- destination: masterGain,
18164
+ destination: getChannelStrip(e.trackId).input,
17561
18165
  pitch: e.pitch,
17562
18166
  volume: e.volume,
17563
18167
  when: e.when,
@@ -17596,7 +18200,7 @@ var createDtmStudio = async (options = {}) => {
17596
18200
  if (!sfInst) return;
17597
18201
  sfInst.play({
17598
18202
  ctx: audioCtx,
17599
- destination: masterGain,
18203
+ destination: getChannelStrip(e.trackId).input,
17600
18204
  pitch: e.pitch,
17601
18205
  volume: e.volume,
17602
18206
  when: e.when,
@@ -17652,7 +18256,7 @@ var createDtmStudio = async (options = {}) => {
17652
18256
  if (!sfInst) return;
17653
18257
  sfInst.play({
17654
18258
  ctx: audioCtx,
17655
- destination: masterGain,
18259
+ destination: getChannelStrip("chord").input,
17656
18260
  pitch: e.pitch,
17657
18261
  volume: e.volume,
17658
18262
  when: e.when,
@@ -17702,6 +18306,9 @@ var createDtmStudio = async (options = {}) => {
17702
18306
  mountedPlayers.length = 0;
17703
18307
  mountedChordPlayers.length = 0;
17704
18308
  mountedEditors.length = 0;
18309
+ clipMeter.dispose();
18310
+ for (const strip of channelStrips.values()) strip.dispose();
18311
+ channelStrips.clear();
17705
18312
  void audioCtx.close();
17706
18313
  };
17707
18314
  const setMasterVolume = (volume) => {