@onjmin/dtm 0.1.90 → 0.1.92

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
@@ -2154,6 +2154,15 @@ var vibratoPitchCurve = (baseHz, preMs) => (tMs) => {
2154
2154
  return baseHz * 2 ** (cents / 1200);
2155
2155
  };
2156
2156
 
2157
+ // src/voice-worker-types.ts
2158
+ var COMPOSITE_ALIAS_SEP = "\0";
2159
+ var packCompositeAlias = (consonantAlias, vowelAlias) => `${COMPOSITE_ALIAS_SEP}${consonantAlias}${COMPOSITE_ALIAS_SEP}${vowelAlias}`;
2160
+ var unpackCompositeAlias = (alias) => {
2161
+ if (!alias.startsWith(COMPOSITE_ALIAS_SEP)) return null;
2162
+ const parts = alias.slice(COMPOSITE_ALIAS_SEP.length).split(COMPOSITE_ALIAS_SEP);
2163
+ return parts.length === 2 ? [parts[0], parts[1]] : null;
2164
+ };
2165
+
2157
2166
  // src/lyrics.ts
2158
2167
  var kanaTable = {
2159
2168
  \u3042: ["", "a"],
@@ -2332,9 +2341,10 @@ var parseLyrics = (mml) => {
2332
2341
  let delay = 0;
2333
2342
  let gender = 50;
2334
2343
  let breathiness = 50;
2344
+ let tension = 50;
2335
2345
  let octaveUnison = "none";
2336
2346
  const modelMatch = rest.match(
2337
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2347
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghewt]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2338
2348
  );
2339
2349
  let model = "";
2340
2350
  const metaTokens = [];
@@ -2403,6 +2413,13 @@ var parseLyrics = (mml) => {
2403
2413
  rest = rest.substring(hMatch[0].length).trim();
2404
2414
  continue;
2405
2415
  }
2416
+ const tMatch = rest.match(/^t(\d+)/i);
2417
+ if (tMatch) {
2418
+ tension = clamp2(Number.parseInt(tMatch[1], 10), 0, 100);
2419
+ metaTokens.push(tMatch[0]);
2420
+ rest = rest.substring(tMatch[0].length).trim();
2421
+ continue;
2422
+ }
2406
2423
  const eMatch = rest.match(/^e(\d+)/i);
2407
2424
  if (eMatch) {
2408
2425
  delay = clamp2(Number.parseInt(eMatch[1], 10), 0, 100);
@@ -2436,6 +2453,7 @@ var parseLyrics = (mml) => {
2436
2453
  delay,
2437
2454
  gender,
2438
2455
  breathiness,
2456
+ tension,
2439
2457
  octaveUnison,
2440
2458
  syllables,
2441
2459
  metaText: metaTokens.join(" "),
@@ -2757,8 +2775,33 @@ var resolveKoeAlias = (hasAlias, pitchTokens, syl, prevVowel, noteNum) => {
2757
2775
  const hit = tryAlias(base);
2758
2776
  if (hit) return hit;
2759
2777
  }
2778
+ if (cons && vow) {
2779
+ const vowelAlias = tryAlias(vow) ?? tryAlias(`${pv} ${vow}`);
2780
+ const consAlias = vowelAlias ? tryAlias(cons) : null;
2781
+ if (vowelAlias && consAlias)
2782
+ return packCompositeAlias(consAlias, vowelAlias);
2783
+ }
2760
2784
  return null;
2761
2785
  };
2786
+ var COMPOSITE_SPLICE_XFADE_SEC = 5e-3;
2787
+ var spliceCompositePcm = (consonantPcm, vowelPcm, sampleRate) => {
2788
+ if (consonantPcm.length === 0 || vowelPcm.length === 0) return null;
2789
+ const xfade = Math.min(
2790
+ Math.floor(sampleRate * COMPOSITE_SPLICE_XFADE_SEC),
2791
+ consonantPcm.length,
2792
+ vowelPcm.length
2793
+ );
2794
+ const pcm = new Float64Array(consonantPcm.length + vowelPcm.length - xfade);
2795
+ pcm.set(consonantPcm, 0);
2796
+ for (let i2 = 0; i2 < xfade; i2++) {
2797
+ const t = (i2 + 1) / (xfade + 1);
2798
+ const idx = consonantPcm.length - xfade + i2;
2799
+ pcm[idx] = consonantPcm[idx] * (1 - t) + vowelPcm[i2] * t;
2800
+ }
2801
+ pcm.set(vowelPcm.subarray(xfade), consonantPcm.length);
2802
+ const consonantMs = (consonantPcm.length - xfade / 2) / sampleRate * 1e3;
2803
+ return { pcm, preMs: consonantMs, consonantMs };
2804
+ };
2762
2805
  var createLocalBackend = async (options) => {
2763
2806
  const bank = await VoiceBank.load(options.koe);
2764
2807
  const worldline = options.lightweight ? null : await Worldline.load({
@@ -2773,7 +2816,40 @@ var createLocalBackend = async (options) => {
2773
2816
  }
2774
2817
  return p;
2775
2818
  };
2819
+ const renderComposite = async (consonantAlias, vowelAlias, pitch, durationMs, vibrato, expr) => {
2820
+ if (!worldline) return null;
2821
+ const [consonantPcm, vowelPcm] = await Promise.all([
2822
+ getPcm(consonantAlias),
2823
+ getPcm(vowelAlias)
2824
+ ]);
2825
+ if (!consonantPcm || !vowelPcm) return null;
2826
+ const spliced = spliceCompositePcm(consonantPcm, vowelPcm, KOE_SAMPLE_RATE);
2827
+ if (!spliced) return null;
2828
+ const targetHz = midiToFreq(pitch);
2829
+ const audio = worldline.renderNote({
2830
+ pcm: spliced.pcm,
2831
+ pitch: vibrato ? vibratoPitchCurve(targetHz, spliced.preMs) : targetHz,
2832
+ durationMs,
2833
+ preMs: spliced.preMs,
2834
+ consonantMs: spliced.consonantMs,
2835
+ gender: expr?.gender,
2836
+ breathiness: expr?.breathiness,
2837
+ tension: expr?.tension
2838
+ });
2839
+ return audio ? { pcm: audio, preSec: spliced.preMs / 1e3, rate: 1 } : null;
2840
+ };
2776
2841
  const renderAlias = async (alias, pitch, durationMs, vibrato, expr) => {
2842
+ const composite = unpackCompositeAlias(alias);
2843
+ if (composite) {
2844
+ return renderComposite(
2845
+ composite[0],
2846
+ composite[1],
2847
+ pitch,
2848
+ durationMs,
2849
+ vibrato,
2850
+ expr
2851
+ );
2852
+ }
2777
2853
  const pcm = await getPcm(alias);
2778
2854
  if (!pcm || pcm.length === 0) return null;
2779
2855
  const entry = bank.manifest.phonemes[alias];
@@ -2786,7 +2862,8 @@ var createLocalBackend = async (options) => {
2786
2862
  durationMs,
2787
2863
  ...lead,
2788
2864
  gender: expr?.gender,
2789
- breathiness: expr?.breathiness
2865
+ breathiness: expr?.breathiness,
2866
+ tension: expr?.tension
2790
2867
  });
2791
2868
  if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
2792
2869
  }
@@ -2867,7 +2944,8 @@ var createWorkerBackend = async (workerUrl, options) => {
2867
2944
  durationMs,
2868
2945
  vibrato,
2869
2946
  gender: expr?.gender,
2870
- breathiness: expr?.breathiness
2947
+ breathiness: expr?.breathiness,
2948
+ tension: expr?.tension
2871
2949
  });
2872
2950
  });
2873
2951
  return {
@@ -2896,7 +2974,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2896
2974
  const inflight = /* @__PURE__ */ new Map();
2897
2975
  const active = /* @__PURE__ */ new Set();
2898
2976
  let prevVowel = "";
2899
- 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)}` : ""}`;
2977
+ 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)}` : ""}${expr?.tension !== void 0 ? `|t${Math.round(expr.tension * 100)}` : ""}`;
2900
2978
  const renderInto = (alias, pitch, durationMs, vibrato, expr) => {
2901
2979
  const key = keyOf(alias, pitch, durationMs, vibrato, expr);
2902
2980
  const existing = renderCache.get(key);
@@ -3127,7 +3205,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3127
3205
  let n = 0;
3128
3206
  const expr = {
3129
3207
  gender: track.gender,
3130
- breathiness: track.breathiness
3208
+ breathiness: track.breathiness,
3209
+ tension: track.tension
3131
3210
  };
3132
3211
  forEachSungNote(track, (note, prevVowel) => {
3133
3212
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
@@ -3214,7 +3293,11 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3214
3293
  pitch,
3215
3294
  note.durationSec * 1e3,
3216
3295
  track.vibrato,
3217
- { gender: track.gender, breathiness: track.breathiness }
3296
+ {
3297
+ gender: track.gender,
3298
+ breathiness: track.breathiness,
3299
+ tension: track.tension
3300
+ }
3218
3301
  );
3219
3302
  if (session !== streamSession) return;
3220
3303
  if (key) {
@@ -7905,14 +7988,33 @@ var DAW_CSS = `
7905
7988
  border-right: 2px solid var(--c-black);
7906
7989
  border-bottom: 2px solid var(--c-black);
7907
7990
  }
7991
+ /* \u30A2\u30A4\u30B3\u30F3\u304C\u753B\u9762\u4E0A\u7AEF\u306B\u8FD1\u304F\u4E0A\u306B\u51FA\u3059\u4F59\u767D\u304C\u7121\u3044\u5834\u5408\u306F\u4E0B\u306B\u53CD\u8EE2\u8868\u793A\u3059\u308B\uFF08positionBalloon \u304C\u4ED8\u4E0E\uFF09 */
7992
+ .dtm-player-balloon--below {
7993
+ transform: translate(-50%, 0);
7994
+ }
7995
+ .dtm-player-balloon--below::after {
7996
+ bottom: auto;
7997
+ top: -6px;
7998
+ border-right: none;
7999
+ border-bottom: none;
8000
+ border-left: 2px solid var(--c-black);
8001
+ border-top: 2px solid var(--c-black);
8002
+ }
7908
8003
  .dtm-player-balloon--visible {
7909
8004
  display: block;
7910
8005
  animation: dtm-balloon-fade-in 0.1s steps(2);
7911
8006
  }
8007
+ .dtm-player-balloon--below.dtm-player-balloon--visible {
8008
+ animation-name: dtm-balloon-fade-in-below;
8009
+ }
7912
8010
  @keyframes dtm-balloon-fade-in {
7913
8011
  from { opacity: 0; transform: translate(-50%, -100%) translateY(4px); }
7914
8012
  to { opacity: 1; transform: translate(-50%, -100%) translateY(0); }
7915
8013
  }
8014
+ @keyframes dtm-balloon-fade-in-below {
8015
+ from { opacity: 0; transform: translate(-50%, 0) translateY(-4px); }
8016
+ to { opacity: 1; transform: translate(-50%, 0) translateY(0); }
8017
+ }
7916
8018
  @keyframes dtm-emoji-jump {
7917
8019
  0% { transform: translateY(0); }
7918
8020
  35% { transform: translateY(-5px); }
@@ -8338,9 +8440,17 @@ var balloonAnchors = /* @__PURE__ */ new WeakMap();
8338
8440
  var positionBalloon = (balloonEl) => {
8339
8441
  const anchor = balloonAnchors.get(balloonEl);
8340
8442
  if (!anchor) return;
8443
+ const win = balloonEl.ownerDocument?.defaultView ?? window;
8341
8444
  const rect = anchor.getBoundingClientRect();
8342
- balloonEl.style.left = `${rect.left + rect.width / 2}px`;
8343
- balloonEl.style.top = `${rect.top - 6}px`;
8445
+ const centerX = rect.left + rect.width / 2;
8446
+ const balloonRect = balloonEl.getBoundingClientRect();
8447
+ const showBelow = rect.top - balloonRect.height - 6 < 0;
8448
+ balloonEl.classList.toggle("dtm-player-balloon--below", showBelow);
8449
+ balloonEl.style.top = showBelow ? `${rect.bottom + 6}px` : `${rect.top - 6}px`;
8450
+ const halfWidth = balloonRect.width / 2 || 40;
8451
+ const minX = halfWidth + 4;
8452
+ const maxX = win.innerWidth - halfWidth - 4;
8453
+ balloonEl.style.left = `${Math.min(Math.max(centerX, minX), maxX)}px`;
8344
8454
  };
8345
8455
  var balloonListenersBound = false;
8346
8456
  var ensureBalloonListeners = () => {
@@ -8366,6 +8476,7 @@ var hideActiveBalloon = () => {
8366
8476
  }
8367
8477
  };
8368
8478
  var showBalloon = (balloonEl) => {
8479
+ balloonEl.classList.add("dtm-player-balloon--visible");
8369
8480
  positionBalloon(balloonEl);
8370
8481
  if (activeBalloonEl === balloonEl) {
8371
8482
  if (activeBalloonTimer) {
@@ -8374,7 +8485,6 @@ var showBalloon = (balloonEl) => {
8374
8485
  } else {
8375
8486
  hideActiveBalloon();
8376
8487
  activeBalloonEl = balloonEl;
8377
- balloonEl.classList.add("dtm-player-balloon--visible");
8378
8488
  }
8379
8489
  activeBalloonTimer = setTimeout(() => {
8380
8490
  hideActiveBalloon();
@@ -8676,10 +8786,6 @@ var mountMmlPlayer = (target, mml, options = {}) => {
8676
8786
  const textSpan = doc.createElement("span");
8677
8787
  textSpan.textContent = "\u{1F97A}";
8678
8788
  em.appendChild(textSpan);
8679
- em.addEventListener("click", (e) => {
8680
- e.stopPropagation();
8681
- toggleMute(index);
8682
- });
8683
8789
  mmlHeader.appendChild(em);
8684
8790
  emojiEls.push(em);
8685
8791
  emojiByTrack.set(index, em);
@@ -9441,6 +9547,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9441
9547
  delaySend: (lt.delay ?? 0) / 100,
9442
9548
  gender: (lt.gender ?? 50) / 100,
9443
9549
  breathiness: (lt.breathiness ?? 50) / 100,
9550
+ tension: (lt.tension ?? 50) / 100,
9444
9551
  octaveUnison: lt.octaveUnison,
9445
9552
  notes
9446
9553
  };
@@ -13291,6 +13398,16 @@ var BREATHINESS_INFO_HTML = `
13291
13398
  <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>
13292
13399
  </div>
13293
13400
  `;
13401
+ var TENSION_INFO_HTML = `
13402
+ <div class="dtm-modal-body-content">
13403
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13404
+ <p>\u58F0\u306E\u5F35\u308A\uFF0F\u62BC\u3057\u51FA\u3057\u306E\u5F37\u3055\u3067\u3059\u300250\u304C\u7121\u5909\u5316\u3001\u5927\u304D\u3044\u307B\u3069\u5F35\u3063\u305F\u30FB\u62BC\u3057\u305F\u58F0\uFF08\u300C\u3053\u3076\u3057\u300D\u5BC4\u308A\u3001\u529B\u5F37\u304F\u6B4C\u308F\u305B\u308B\uFF09\u306B\u306A\u308A\u3001\u5C0F\u3055\u3044\u307B\u3069\u8131\u529B\u3057\u305F\u30EA\u30E9\u30C3\u30AF\u30B9\u3057\u305F\u58F0\u306B\u306A\u308A\u307E\u3059\u3002</p>
13405
+ <h4>\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306E\u9055\u3044</h4>
13406
+ <p>\u30D6\u30EC\u30B7\u30CD\u30B9\u306F\u606F\u6210\u5206\u306E\u91CF\uFF08\u82AF\u306E\u3042\u308B\u58F0\u21D4\u606F\u3063\u307D\u3044\u58F0\uFF09\u3092\u52D5\u304B\u3057\u307E\u3059\u304C\u3001\u30C6\u30F3\u30B7\u30E7\u30F3\u306F\u58F0\u5E2F\u306E\u7DE0\u307E\u308A\u30FB\u62BC\u3057\u306E\u5F37\u3055\uFF08\u8131\u529B\u21D4\u5F35\u3063\u305F\u58F0\uFF09\u3092\u52D5\u304B\u3057\u307E\u3059\u3002\u30C6\u30F3\u30B7\u30E7\u30F3\u3092\u4E0A\u3052\u308B\u3068\u30B5\u30D3\u306E\u529B\u5F37\u3055\u3001\u4E0B\u3052\u308B\u3068\u7A4F\u3084\u304B\u306A\u56C1\u304D\u306B\u5BC4\u305B\u305F\u6B4C\u308F\u305B\u65B9\u306B\u306A\u308A\u307E\u3059\u3002</p>
13407
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13408
+ <p>\u30D6\u30EC\u30B7\u30CD\u30B9\u3092\u4E0B\u3052\u3064\u3064\u30C6\u30F3\u30B7\u30E7\u30F3\u3092\u4E0A\u3052\u308B\u3068\u3001\u3088\u308A\u82AF\u304C\u3042\u308A\u529B\u5F37\u3044\u6B4C\u58F0\u306B\u306A\u308A\u307E\u3059\u3002\u9006\u306B\u30D6\u30EC\u30B7\u30CD\u30B9\u3092\u4E0A\u3052\u3064\u3064\u30C6\u30F3\u30B7\u30E7\u30F3\u3092\u4E0B\u3052\u308B\u3068\u3001\u3088\u308A\u8131\u529B\u3057\u305F\u606F\u3063\u307D\u3044\u6B4C\u58F0\u306B\u306A\u308A\u307E\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>
13409
+ </div>
13410
+ `;
13294
13411
  var LYRIC_REVERB_INFO_HTML = `
13295
13412
  <div class="dtm-modal-body-content">
13296
13413
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -13815,6 +13932,7 @@ var mountDAW = (target, options = {}) => {
13815
13932
  vocalDelay: t.vocalDelay,
13816
13933
  vocalGender: t.vocalGender,
13817
13934
  vocalBreathiness: t.vocalBreathiness,
13935
+ vocalTension: t.vocalTension,
13818
13936
  vocalOctaveUnison: t.vocalOctaveUnison
13819
13937
  };
13820
13938
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
@@ -13880,6 +13998,7 @@ var mountDAW = (target, options = {}) => {
13880
13998
  vocalDelay: 0,
13881
13999
  vocalGender: 50,
13882
14000
  vocalBreathiness: 50,
14001
+ vocalTension: 50,
13883
14002
  vocalOctaveUnison: "none",
13884
14003
  trackInstrument: "",
13885
14004
  trackCompression: 0,
@@ -13911,6 +14030,7 @@ var mountDAW = (target, options = {}) => {
13911
14030
  delay: t.vocalDelay,
13912
14031
  gender: t.vocalGender,
13913
14032
  breathiness: t.vocalBreathiness,
14033
+ tension: t.vocalTension,
13914
14034
  octaveUnison: t.vocalOctaveUnison,
13915
14035
  syllables
13916
14036
  });
@@ -14721,6 +14841,7 @@ var mountDAW = (target, options = {}) => {
14721
14841
  delaySend: (lt.delay ?? 0) / 100,
14722
14842
  gender: (lt.gender ?? 50) / 100,
14723
14843
  breathiness: (lt.breathiness ?? 50) / 100,
14844
+ tension: (lt.tension ?? 50) / 100,
14724
14845
  octaveUnison: lt.octaveUnison,
14725
14846
  notes
14726
14847
  };
@@ -15149,6 +15270,12 @@ var mountDAW = (target, options = {}) => {
15149
15270
  <span class="dtm-label" data-dtm="lyric-breathiness-label"></span>
15150
15271
  <button class="dtm-infobtn" data-dtm="lyric-breathiness-info" title="\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15151
15272
  </div>
15273
+ <div class="dtm-row">
15274
+ <span class="dtm-label">\u30C6\u30F3\u30B7\u30E7\u30F3</span>
15275
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-tension" min="0" max="100" aria-label="\u30C6\u30F3\u30B7\u30E7\u30F3\uFF08\u5F35\u308A/\u529B\u5F37\u3055\u3001koe\u97F3\u6E90\u9650\u5B9A\uFF09">
15276
+ <span class="dtm-label" data-dtm="lyric-tension-label"></span>
15277
+ <button class="dtm-infobtn" data-dtm="lyric-tension-info" title="\u30C6\u30F3\u30B7\u30E7\u30F3\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15278
+ </div>
15152
15279
  <div class="dtm-row">
15153
15280
  <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
15154
15281
  <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
@@ -15252,6 +15379,18 @@ var mountDAW = (target, options = {}) => {
15252
15379
  lyricBreathinessInfo.addEventListener("click", () => {
15253
15380
  showModal("\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC", BREATHINESS_INFO_HTML);
15254
15381
  });
15382
+ const lyricTension = lyricDiv.querySelector(
15383
+ '[data-dtm="lyric-tension"]'
15384
+ );
15385
+ const lyricTensionLabel = lyricDiv.querySelector(
15386
+ '[data-dtm="lyric-tension-label"]'
15387
+ );
15388
+ const lyricTensionInfo = lyricDiv.querySelector(
15389
+ '[data-dtm="lyric-tension-info"]'
15390
+ );
15391
+ lyricTensionInfo.addEventListener("click", () => {
15392
+ showModal("\u30C6\u30F3\u30B7\u30E7\u30F3\u306E\u89E3\u8AAC", TENSION_INFO_HTML);
15393
+ });
15255
15394
  const lyricVibrato = lyricDiv.querySelector(
15256
15395
  '[data-dtm="lyric-vibrato"]'
15257
15396
  );
@@ -15339,6 +15478,8 @@ var mountDAW = (target, options = {}) => {
15339
15478
  lyricGenderLabel.textContent = `${active.vocalGender}`;
15340
15479
  lyricBreathiness.value = String(active.vocalBreathiness);
15341
15480
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
15481
+ lyricTension.value = String(active.vocalTension);
15482
+ lyricTensionLabel.textContent = `${active.vocalTension}`;
15342
15483
  lyricVibrato.checked = active.vocalVibrato;
15343
15484
  lyricOctaveUnison.value = active.vocalOctaveUnison;
15344
15485
  const updateLyricCount = () => {
@@ -15509,6 +15650,11 @@ var mountDAW = (target, options = {}) => {
15509
15650
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
15510
15651
  fireLyricsChange(active);
15511
15652
  });
15653
+ lyricTension.addEventListener("input", () => {
15654
+ active.vocalTension = Number.parseInt(lyricTension.value, 10);
15655
+ lyricTensionLabel.textContent = `${active.vocalTension}`;
15656
+ fireLyricsChange(active);
15657
+ });
15512
15658
  }
15513
15659
  if (active.config.id === "chord" && showChord) {
15514
15660
  const div = document.createElement("div");
@@ -15713,6 +15859,7 @@ var mountDAW = (target, options = {}) => {
15713
15859
  del: t.vocalDelay,
15714
15860
  gen: t.vocalGender,
15715
15861
  bre: t.vocalBreathiness,
15862
+ ten: t.vocalTension,
15716
15863
  uni: t.vocalOctaveUnison
15717
15864
  })).filter(
15718
15865
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
@@ -15727,6 +15874,7 @@ var mountDAW = (target, options = {}) => {
15727
15874
  x2.del === 0 ? "" : `e${x2.del}`,
15728
15875
  x2.gen === 50 ? "" : `g${x2.gen}`,
15729
15876
  x2.bre === 50 ? "" : `h${x2.bre}`,
15877
+ x2.ten === 50 ? "" : `t${x2.ten}`,
15730
15878
  OCTAVE_UNISON_TOKEN[x2.uni]
15731
15879
  ].filter((s) => s.length > 0).join(" ");
15732
15880
  const head = params ? `${x2.model} ${params}` : x2.model;
@@ -15969,6 +16117,7 @@ var mountDAW = (target, options = {}) => {
15969
16117
  active.vocalDelay = 0;
15970
16118
  active.vocalGender = 50;
15971
16119
  active.vocalBreathiness = 50;
16120
+ active.vocalTension = 50;
15972
16121
  active.vocalOctaveUnison = "none";
15973
16122
  } else {
15974
16123
  for (const t of trackStates) {
@@ -15983,6 +16132,7 @@ var mountDAW = (target, options = {}) => {
15983
16132
  t.vocalDelay = 0;
15984
16133
  t.vocalGender = 50;
15985
16134
  t.vocalBreathiness = 50;
16135
+ t.vocalTension = 50;
15986
16136
  t.vocalOctaveUnison = "none";
15987
16137
  }
15988
16138
  }
@@ -16001,6 +16151,8 @@ var mountDAW = (target, options = {}) => {
16001
16151
  t.vocalDelay = lt.delay ?? 0;
16002
16152
  t.vocalGender = lt.gender ?? 50;
16003
16153
  t.vocalBreathiness = lt.breathiness ?? 50;
16154
+ t.vocalTension = lt.tension ?? 50;
16155
+ t.vocalTension = lt.tension ?? 50;
16004
16156
  t.vocalOctaveUnison = lt.octaveUnison ?? "none";
16005
16157
  });
16006
16158
  for (const p of placements) {
@@ -16085,6 +16237,7 @@ var mountDAW = (target, options = {}) => {
16085
16237
  active.vocalDelay = 0;
16086
16238
  active.vocalGender = 50;
16087
16239
  active.vocalBreathiness = 50;
16240
+ active.vocalTension = 50;
16088
16241
  active.vocalOctaveUnison = "none";
16089
16242
  } else {
16090
16243
  clearAll();
@@ -16101,6 +16254,7 @@ var mountDAW = (target, options = {}) => {
16101
16254
  t.vocalDelay = 0;
16102
16255
  t.vocalGender = 50;
16103
16256
  t.vocalBreathiness = 50;
16257
+ t.vocalTension = 50;
16104
16258
  t.vocalOctaveUnison = "none";
16105
16259
  }
16106
16260
  }
@@ -17465,6 +17619,7 @@ var mountDAW = (target, options = {}) => {
17465
17619
  t.vocalDelay = data.vocalDelay ?? 0;
17466
17620
  t.vocalGender = data.vocalGender ?? 50;
17467
17621
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17622
+ t.vocalTension = data.vocalTension ?? 50;
17468
17623
  t.vocalOctaveUnison = data.vocalOctaveUnison ?? "none";
17469
17624
  },
17470
17625
  applyTrackInstrument: (trackIndex, instrumentName) => {
@@ -17587,6 +17742,7 @@ var playSingingMML = async (mml, options = {}) => {
17587
17742
  delaySend: (lt.delay ?? 0) / 100,
17588
17743
  gender: (lt.gender ?? 50) / 100,
17589
17744
  breathiness: (lt.breathiness ?? 50) / 100,
17745
+ tension: (lt.tension ?? 50) / 100,
17590
17746
  octaveUnison: lt.octaveUnison,
17591
17747
  notes
17592
17748
  };
@@ -525,6 +525,14 @@
525
525
  return baseHz * 2 ** (cents / 1200);
526
526
  };
527
527
 
528
+ // src/voice-worker-types.ts
529
+ var COMPOSITE_ALIAS_SEP = "\0";
530
+ var unpackCompositeAlias = (alias) => {
531
+ if (!alias.startsWith(COMPOSITE_ALIAS_SEP)) return null;
532
+ const parts = alias.slice(COMPOSITE_ALIAS_SEP.length).split(COMPOSITE_ALIAS_SEP);
533
+ return parts.length === 2 ? [parts[0], parts[1]] : null;
534
+ };
535
+
528
536
  // src/voice-worker.ts
529
537
  var KOE_SAMPLE_RATE = 48e3;
530
538
  var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
@@ -540,8 +548,62 @@
540
548
  }
541
549
  return p;
542
550
  };
543
- var renderAlias = async (alias, pitch, durationMs, vibrato, gender, breathiness) => {
551
+ var COMPOSITE_SPLICE_XFADE_SEC = 5e-3;
552
+ var spliceCompositePcm = (consonantPcm, vowelPcm, sampleRate) => {
553
+ if (consonantPcm.length === 0 || vowelPcm.length === 0) return null;
554
+ const xfade = Math.min(
555
+ Math.floor(sampleRate * COMPOSITE_SPLICE_XFADE_SEC),
556
+ consonantPcm.length,
557
+ vowelPcm.length
558
+ );
559
+ const pcm = new Float64Array(consonantPcm.length + vowelPcm.length - xfade);
560
+ pcm.set(consonantPcm, 0);
561
+ for (let i2 = 0; i2 < xfade; i2++) {
562
+ const t = (i2 + 1) / (xfade + 1);
563
+ const idx = consonantPcm.length - xfade + i2;
564
+ pcm[idx] = consonantPcm[idx] * (1 - t) + vowelPcm[i2] * t;
565
+ }
566
+ pcm.set(vowelPcm.subarray(xfade), consonantPcm.length);
567
+ const consonantMs = (consonantPcm.length - xfade / 2) / sampleRate * 1e3;
568
+ return { pcm, preMs: consonantMs, consonantMs };
569
+ };
570
+ var renderComposite = async (consonantAlias, vowelAlias, pitch, durationMs, vibrato, gender, breathiness, tension) => {
571
+ if (!worldline) return null;
572
+ const [consonantPcm, vowelPcm] = await Promise.all([
573
+ getPcm(consonantAlias),
574
+ getPcm(vowelAlias)
575
+ ]);
576
+ if (!consonantPcm || !vowelPcm) return null;
577
+ const spliced = spliceCompositePcm(consonantPcm, vowelPcm, KOE_SAMPLE_RATE);
578
+ if (!spliced) return null;
579
+ const targetHz = midiToFreq(pitch);
580
+ const audio = worldline.renderNote({
581
+ pcm: spliced.pcm,
582
+ pitch: vibrato ? vibratoPitchCurve(targetHz, spliced.preMs) : targetHz,
583
+ durationMs,
584
+ preMs: spliced.preMs,
585
+ consonantMs: spliced.consonantMs,
586
+ gender,
587
+ breathiness,
588
+ tension
589
+ });
590
+ return audio ? { pcm: audio, preSec: spliced.preMs / 1e3, rate: 1 } : null;
591
+ };
592
+ var renderAlias = async (alias, pitch, durationMs, vibrato, gender, breathiness, tension) => {
544
593
  if (!bank) return null;
594
+ const composite = unpackCompositeAlias(alias);
595
+ if (composite) {
596
+ return renderComposite(
597
+ composite[0],
598
+ composite[1],
599
+ pitch,
600
+ durationMs,
601
+ vibrato,
602
+ gender,
603
+ breathiness,
604
+ tension
605
+ );
606
+ }
545
607
  const pcm = await getPcm(alias);
546
608
  if (!pcm || pcm.length === 0) return null;
547
609
  const entry = bank.manifest.phonemes[alias];
@@ -554,7 +616,8 @@
554
616
  durationMs,
555
617
  ...lead,
556
618
  gender,
557
- breathiness
619
+ breathiness,
620
+ tension
558
621
  });
559
622
  if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
560
623
  }
@@ -586,7 +649,16 @@
586
649
  return;
587
650
  }
588
651
  if (msg.type === "render") {
589
- const { id, alias, pitch, durationMs, vibrato, gender, breathiness } = msg;
652
+ const {
653
+ id,
654
+ alias,
655
+ pitch,
656
+ durationMs,
657
+ vibrato,
658
+ gender,
659
+ breathiness,
660
+ tension
661
+ } = msg;
590
662
  try {
591
663
  const out = await renderAlias(
592
664
  alias,
@@ -594,7 +666,8 @@
594
666
  durationMs,
595
667
  vibrato,
596
668
  gender,
597
- breathiness
669
+ breathiness,
670
+ tension
598
671
  );
599
672
  if (out) {
600
673
  wself.postMessage(
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "onjmin",
3
3
  "license": "MIT",
4
4
  "name": "@onjmin/dtm",
5
- "version": "0.1.90",
5
+ "version": "0.1.92",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {