@onjmin/dtm 0.1.89 → 0.1.91

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.d.mts CHANGED
@@ -297,6 +297,8 @@ type VoiceExpression = {
297
297
  gender?: number;
298
298
  /** ブレシネス(息成分)。大きいほど息っぽく(ささやき寄り)。 */
299
299
  breathiness?: number;
300
+ /** テンション(張り/力強さ)。大きいほど張った・押した声(こぶし寄り、力強く歌う)。 */
301
+ tension?: number;
300
302
  };
301
303
  /** 歌唱合成モデルの実装シグネチャ */
302
304
  type VoiceModel = {
@@ -456,6 +458,11 @@ type StreamVoiceTrack = {
456
458
  * — klattフォールバックには効かない。
457
459
  */
458
460
  breathiness?: number;
461
+ /**
462
+ * テンション(張り/力強さ、"こぶし"寄り)0-1。既定0.5(無変化)。koe音源(Worldline)限定
463
+ * — klattフォールバックには効かない。
464
+ */
465
+ tension?: number;
459
466
  /**
460
467
  * オクターブユニゾン。各音節をもう1声、1オクターブ上/下(控えめな音量)で重ねて発音し、
461
468
  * 声に厚み(下)または煌びやかさ(上)を足す。既定 "none"(重ねない)。
@@ -680,6 +687,7 @@ type LyricSyncData = {
680
687
  vocalDelay?: number;
681
688
  vocalGender?: number;
682
689
  vocalBreathiness?: number;
690
+ vocalTension?: number;
683
691
  vocalOctaveUnison?: OctaveUnisonMode;
684
692
  };
685
693
  /** パッチ送受信用ノートデータ(ローカルIDを持たない)。 */
@@ -854,6 +862,12 @@ type LyricTrack = {
854
862
  * MMLでは `@@n klatt h70 …` のように h トークンで付与する。
855
863
  */
856
864
  breathiness?: number;
865
+ /**
866
+ * テンション(張り/力強さ)0-100。既定50(無変化)。大きいほど張った・押した声
867
+ * (こぶし寄り、力強く歌わせる)、小さいほど脱力・リラックス(ブレシネス寄り)になる。
868
+ * MMLでは `@@n klatt t70 …` のように t トークンで付与する。
869
+ */
870
+ tension?: number;
857
871
  /**
858
872
  * オクターブユニゾン。既定"none"。
859
873
  * "down"/"up"/"both" にすると各音節をもう1オクターブ上/下/両方(控えめな音量)で
package/dist/index.d.ts CHANGED
@@ -297,6 +297,8 @@ type VoiceExpression = {
297
297
  gender?: number;
298
298
  /** ブレシネス(息成分)。大きいほど息っぽく(ささやき寄り)。 */
299
299
  breathiness?: number;
300
+ /** テンション(張り/力強さ)。大きいほど張った・押した声(こぶし寄り、力強く歌う)。 */
301
+ tension?: number;
300
302
  };
301
303
  /** 歌唱合成モデルの実装シグネチャ */
302
304
  type VoiceModel = {
@@ -456,6 +458,11 @@ type StreamVoiceTrack = {
456
458
  * — klattフォールバックには効かない。
457
459
  */
458
460
  breathiness?: number;
461
+ /**
462
+ * テンション(張り/力強さ、"こぶし"寄り)0-1。既定0.5(無変化)。koe音源(Worldline)限定
463
+ * — klattフォールバックには効かない。
464
+ */
465
+ tension?: number;
459
466
  /**
460
467
  * オクターブユニゾン。各音節をもう1声、1オクターブ上/下(控えめな音量)で重ねて発音し、
461
468
  * 声に厚み(下)または煌びやかさ(上)を足す。既定 "none"(重ねない)。
@@ -680,6 +687,7 @@ type LyricSyncData = {
680
687
  vocalDelay?: number;
681
688
  vocalGender?: number;
682
689
  vocalBreathiness?: number;
690
+ vocalTension?: number;
683
691
  vocalOctaveUnison?: OctaveUnisonMode;
684
692
  };
685
693
  /** パッチ送受信用ノートデータ(ローカルIDを持たない)。 */
@@ -854,6 +862,12 @@ type LyricTrack = {
854
862
  * MMLでは `@@n klatt h70 …` のように h トークンで付与する。
855
863
  */
856
864
  breathiness?: number;
865
+ /**
866
+ * テンション(張り/力強さ)0-100。既定50(無変化)。大きいほど張った・押した声
867
+ * (こぶし寄り、力強く歌わせる)、小さいほど脱力・リラックス(ブレシネス寄り)になる。
868
+ * MMLでは `@@n klatt t70 …` のように t トークンで付与する。
869
+ */
870
+ tension?: number;
857
871
  /**
858
872
  * オクターブユニゾン。既定"none"。
859
873
  * "down"/"up"/"both" にすると各音節をもう1オクターブ上/下/両方(控えめな音量)で
package/dist/index.js CHANGED
@@ -2285,6 +2285,15 @@ var vibratoPitchCurve = (baseHz, preMs) => (tMs) => {
2285
2285
  return baseHz * 2 ** (cents / 1200);
2286
2286
  };
2287
2287
 
2288
+ // src/voice-worker-types.ts
2289
+ var COMPOSITE_ALIAS_SEP = "\0";
2290
+ var packCompositeAlias = (consonantAlias, vowelAlias) => `${COMPOSITE_ALIAS_SEP}${consonantAlias}${COMPOSITE_ALIAS_SEP}${vowelAlias}`;
2291
+ var unpackCompositeAlias = (alias) => {
2292
+ if (!alias.startsWith(COMPOSITE_ALIAS_SEP)) return null;
2293
+ const parts = alias.slice(COMPOSITE_ALIAS_SEP.length).split(COMPOSITE_ALIAS_SEP);
2294
+ return parts.length === 2 ? [parts[0], parts[1]] : null;
2295
+ };
2296
+
2288
2297
  // src/lyrics.ts
2289
2298
  var kanaTable = {
2290
2299
  \u3042: ["", "a"],
@@ -2463,9 +2472,10 @@ var parseLyrics = (mml) => {
2463
2472
  let delay = 0;
2464
2473
  let gender = 50;
2465
2474
  let breathiness = 50;
2475
+ let tension = 50;
2466
2476
  let octaveUnison = "none";
2467
2477
  const modelMatch = rest.match(
2468
- /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghew]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2478
+ /^([a-z_][a-z0-9_]*?)(?=(?:[vqpobrghewt]-?\d)|[^a-z0-9_]|$)(?::(\d+))?/i
2469
2479
  );
2470
2480
  let model = "";
2471
2481
  const metaTokens = [];
@@ -2534,6 +2544,13 @@ var parseLyrics = (mml) => {
2534
2544
  rest = rest.substring(hMatch[0].length).trim();
2535
2545
  continue;
2536
2546
  }
2547
+ const tMatch = rest.match(/^t(\d+)/i);
2548
+ if (tMatch) {
2549
+ tension = clamp2(Number.parseInt(tMatch[1], 10), 0, 100);
2550
+ metaTokens.push(tMatch[0]);
2551
+ rest = rest.substring(tMatch[0].length).trim();
2552
+ continue;
2553
+ }
2537
2554
  const eMatch = rest.match(/^e(\d+)/i);
2538
2555
  if (eMatch) {
2539
2556
  delay = clamp2(Number.parseInt(eMatch[1], 10), 0, 100);
@@ -2567,6 +2584,7 @@ var parseLyrics = (mml) => {
2567
2584
  delay,
2568
2585
  gender,
2569
2586
  breathiness,
2587
+ tension,
2570
2588
  octaveUnison,
2571
2589
  syllables,
2572
2590
  metaText: metaTokens.join(" "),
@@ -2888,8 +2906,33 @@ var resolveKoeAlias = (hasAlias, pitchTokens, syl, prevVowel, noteNum) => {
2888
2906
  const hit = tryAlias(base);
2889
2907
  if (hit) return hit;
2890
2908
  }
2909
+ if (cons && vow) {
2910
+ const vowelAlias = tryAlias(vow) ?? tryAlias(`${pv} ${vow}`);
2911
+ const consAlias = vowelAlias ? tryAlias(cons) : null;
2912
+ if (vowelAlias && consAlias)
2913
+ return packCompositeAlias(consAlias, vowelAlias);
2914
+ }
2891
2915
  return null;
2892
2916
  };
2917
+ var COMPOSITE_SPLICE_XFADE_SEC = 5e-3;
2918
+ var spliceCompositePcm = (consonantPcm, vowelPcm, sampleRate) => {
2919
+ if (consonantPcm.length === 0 || vowelPcm.length === 0) return null;
2920
+ const xfade = Math.min(
2921
+ Math.floor(sampleRate * COMPOSITE_SPLICE_XFADE_SEC),
2922
+ consonantPcm.length,
2923
+ vowelPcm.length
2924
+ );
2925
+ const pcm = new Float64Array(consonantPcm.length + vowelPcm.length - xfade);
2926
+ pcm.set(consonantPcm, 0);
2927
+ for (let i2 = 0; i2 < xfade; i2++) {
2928
+ const t = (i2 + 1) / (xfade + 1);
2929
+ const idx = consonantPcm.length - xfade + i2;
2930
+ pcm[idx] = consonantPcm[idx] * (1 - t) + vowelPcm[i2] * t;
2931
+ }
2932
+ pcm.set(vowelPcm.subarray(xfade), consonantPcm.length);
2933
+ const consonantMs = (consonantPcm.length - xfade / 2) / sampleRate * 1e3;
2934
+ return { pcm, preMs: consonantMs, consonantMs };
2935
+ };
2893
2936
  var createLocalBackend = async (options) => {
2894
2937
  const bank = await VoiceBank.load(options.koe);
2895
2938
  const worldline = options.lightweight ? null : await Worldline.load({
@@ -2904,7 +2947,40 @@ var createLocalBackend = async (options) => {
2904
2947
  }
2905
2948
  return p;
2906
2949
  };
2950
+ const renderComposite = async (consonantAlias, vowelAlias, pitch, durationMs, vibrato, expr) => {
2951
+ if (!worldline) return null;
2952
+ const [consonantPcm, vowelPcm] = await Promise.all([
2953
+ getPcm(consonantAlias),
2954
+ getPcm(vowelAlias)
2955
+ ]);
2956
+ if (!consonantPcm || !vowelPcm) return null;
2957
+ const spliced = spliceCompositePcm(consonantPcm, vowelPcm, KOE_SAMPLE_RATE);
2958
+ if (!spliced) return null;
2959
+ const targetHz = midiToFreq(pitch);
2960
+ const audio = worldline.renderNote({
2961
+ pcm: spliced.pcm,
2962
+ pitch: vibrato ? vibratoPitchCurve(targetHz, spliced.preMs) : targetHz,
2963
+ durationMs,
2964
+ preMs: spliced.preMs,
2965
+ consonantMs: spliced.consonantMs,
2966
+ gender: expr?.gender,
2967
+ breathiness: expr?.breathiness,
2968
+ tension: expr?.tension
2969
+ });
2970
+ return audio ? { pcm: audio, preSec: spliced.preMs / 1e3, rate: 1 } : null;
2971
+ };
2907
2972
  const renderAlias = async (alias, pitch, durationMs, vibrato, expr) => {
2973
+ const composite = unpackCompositeAlias(alias);
2974
+ if (composite) {
2975
+ return renderComposite(
2976
+ composite[0],
2977
+ composite[1],
2978
+ pitch,
2979
+ durationMs,
2980
+ vibrato,
2981
+ expr
2982
+ );
2983
+ }
2908
2984
  const pcm = await getPcm(alias);
2909
2985
  if (!pcm || pcm.length === 0) return null;
2910
2986
  const entry = bank.manifest.phonemes[alias];
@@ -2917,7 +2993,8 @@ var createLocalBackend = async (options) => {
2917
2993
  durationMs,
2918
2994
  ...lead,
2919
2995
  gender: expr?.gender,
2920
- breathiness: expr?.breathiness
2996
+ breathiness: expr?.breathiness,
2997
+ tension: expr?.tension
2921
2998
  });
2922
2999
  if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
2923
3000
  }
@@ -2998,7 +3075,8 @@ var createWorkerBackend = async (workerUrl, options) => {
2998
3075
  durationMs,
2999
3076
  vibrato,
3000
3077
  gender: expr?.gender,
3001
- breathiness: expr?.breathiness
3078
+ breathiness: expr?.breathiness,
3079
+ tension: expr?.tension
3002
3080
  });
3003
3081
  });
3004
3082
  return {
@@ -3027,7 +3105,7 @@ var createKoeVoice = async (ctx, destination, options) => {
3027
3105
  const inflight = /* @__PURE__ */ new Map();
3028
3106
  const active = /* @__PURE__ */ new Set();
3029
3107
  let prevVowel = "";
3030
- 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)}` : ""}`;
3108
+ 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)}` : ""}`;
3031
3109
  const renderInto = (alias, pitch, durationMs, vibrato, expr) => {
3032
3110
  const key = keyOf(alias, pitch, durationMs, vibrato, expr);
3033
3111
  const existing = renderCache.get(key);
@@ -3258,7 +3336,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3258
3336
  let n = 0;
3259
3337
  const expr = {
3260
3338
  gender: track.gender,
3261
- breathiness: track.breathiness
3339
+ breathiness: track.breathiness,
3340
+ tension: track.tension
3262
3341
  };
3263
3342
  forEachSungNote(track, (note, prevVowel) => {
3264
3343
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
@@ -3345,7 +3424,11 @@ var createSingingVoices = (ctx, destination, options = {}) => {
3345
3424
  pitch,
3346
3425
  note.durationSec * 1e3,
3347
3426
  track.vibrato,
3348
- { gender: track.gender, breathiness: track.breathiness }
3427
+ {
3428
+ gender: track.gender,
3429
+ breathiness: track.breathiness,
3430
+ tension: track.tension
3431
+ }
3349
3432
  );
3350
3433
  if (session !== streamSession) return;
3351
3434
  if (key) {
@@ -3879,8 +3962,7 @@ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
3879
3962
  }
3880
3963
  return impulse;
3881
3964
  };
3882
- var REVERB_MAX_WET = 0.6;
3883
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
3965
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100;
3884
3966
 
3885
3967
  // src/sequencer.ts
3886
3968
  var STEPS_PER_BEAT = 48;
@@ -9573,6 +9655,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9573
9655
  delaySend: (lt.delay ?? 0) / 100,
9574
9656
  gender: (lt.gender ?? 50) / 100,
9575
9657
  breathiness: (lt.breathiness ?? 50) / 100,
9658
+ tension: (lt.tension ?? 50) / 100,
9576
9659
  octaveUnison: lt.octaveUnison,
9577
9660
  notes
9578
9661
  };
@@ -13423,6 +13506,16 @@ var BREATHINESS_INFO_HTML = `
13423
13506
  <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>
13424
13507
  </div>
13425
13508
  `;
13509
+ var TENSION_INFO_HTML = `
13510
+ <div class="dtm-modal-body-content">
13511
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13512
+ <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>
13513
+ <h4>\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306E\u9055\u3044</h4>
13514
+ <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>
13515
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13516
+ <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>
13517
+ </div>
13518
+ `;
13426
13519
  var LYRIC_REVERB_INFO_HTML = `
13427
13520
  <div class="dtm-modal-body-content">
13428
13521
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -13947,6 +14040,7 @@ var mountDAW = (target, options = {}) => {
13947
14040
  vocalDelay: t.vocalDelay,
13948
14041
  vocalGender: t.vocalGender,
13949
14042
  vocalBreathiness: t.vocalBreathiness,
14043
+ vocalTension: t.vocalTension,
13950
14044
  vocalOctaveUnison: t.vocalOctaveUnison
13951
14045
  };
13952
14046
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
@@ -14012,6 +14106,7 @@ var mountDAW = (target, options = {}) => {
14012
14106
  vocalDelay: 0,
14013
14107
  vocalGender: 50,
14014
14108
  vocalBreathiness: 50,
14109
+ vocalTension: 50,
14015
14110
  vocalOctaveUnison: "none",
14016
14111
  trackInstrument: "",
14017
14112
  trackCompression: 0,
@@ -14043,6 +14138,7 @@ var mountDAW = (target, options = {}) => {
14043
14138
  delay: t.vocalDelay,
14044
14139
  gender: t.vocalGender,
14045
14140
  breathiness: t.vocalBreathiness,
14141
+ tension: t.vocalTension,
14046
14142
  octaveUnison: t.vocalOctaveUnison,
14047
14143
  syllables
14048
14144
  });
@@ -14853,6 +14949,7 @@ var mountDAW = (target, options = {}) => {
14853
14949
  delaySend: (lt.delay ?? 0) / 100,
14854
14950
  gender: (lt.gender ?? 50) / 100,
14855
14951
  breathiness: (lt.breathiness ?? 50) / 100,
14952
+ tension: (lt.tension ?? 50) / 100,
14856
14953
  octaveUnison: lt.octaveUnison,
14857
14954
  notes
14858
14955
  };
@@ -15281,6 +15378,12 @@ var mountDAW = (target, options = {}) => {
15281
15378
  <span class="dtm-label" data-dtm="lyric-breathiness-label"></span>
15282
15379
  <button class="dtm-infobtn" data-dtm="lyric-breathiness-info" title="\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15283
15380
  </div>
15381
+ <div class="dtm-row">
15382
+ <span class="dtm-label">\u30C6\u30F3\u30B7\u30E7\u30F3</span>
15383
+ <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">
15384
+ <span class="dtm-label" data-dtm="lyric-tension-label"></span>
15385
+ <button class="dtm-infobtn" data-dtm="lyric-tension-info" title="\u30C6\u30F3\u30B7\u30E7\u30F3\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15386
+ </div>
15284
15387
  <div class="dtm-row">
15285
15388
  <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
15286
15389
  <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
@@ -15384,6 +15487,18 @@ var mountDAW = (target, options = {}) => {
15384
15487
  lyricBreathinessInfo.addEventListener("click", () => {
15385
15488
  showModal("\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC", BREATHINESS_INFO_HTML);
15386
15489
  });
15490
+ const lyricTension = lyricDiv.querySelector(
15491
+ '[data-dtm="lyric-tension"]'
15492
+ );
15493
+ const lyricTensionLabel = lyricDiv.querySelector(
15494
+ '[data-dtm="lyric-tension-label"]'
15495
+ );
15496
+ const lyricTensionInfo = lyricDiv.querySelector(
15497
+ '[data-dtm="lyric-tension-info"]'
15498
+ );
15499
+ lyricTensionInfo.addEventListener("click", () => {
15500
+ showModal("\u30C6\u30F3\u30B7\u30E7\u30F3\u306E\u89E3\u8AAC", TENSION_INFO_HTML);
15501
+ });
15387
15502
  const lyricVibrato = lyricDiv.querySelector(
15388
15503
  '[data-dtm="lyric-vibrato"]'
15389
15504
  );
@@ -15471,6 +15586,8 @@ var mountDAW = (target, options = {}) => {
15471
15586
  lyricGenderLabel.textContent = `${active.vocalGender}`;
15472
15587
  lyricBreathiness.value = String(active.vocalBreathiness);
15473
15588
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
15589
+ lyricTension.value = String(active.vocalTension);
15590
+ lyricTensionLabel.textContent = `${active.vocalTension}`;
15474
15591
  lyricVibrato.checked = active.vocalVibrato;
15475
15592
  lyricOctaveUnison.value = active.vocalOctaveUnison;
15476
15593
  const updateLyricCount = () => {
@@ -15641,6 +15758,11 @@ var mountDAW = (target, options = {}) => {
15641
15758
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
15642
15759
  fireLyricsChange(active);
15643
15760
  });
15761
+ lyricTension.addEventListener("input", () => {
15762
+ active.vocalTension = Number.parseInt(lyricTension.value, 10);
15763
+ lyricTensionLabel.textContent = `${active.vocalTension}`;
15764
+ fireLyricsChange(active);
15765
+ });
15644
15766
  }
15645
15767
  if (active.config.id === "chord" && showChord) {
15646
15768
  const div = document.createElement("div");
@@ -15845,6 +15967,7 @@ var mountDAW = (target, options = {}) => {
15845
15967
  del: t.vocalDelay,
15846
15968
  gen: t.vocalGender,
15847
15969
  bre: t.vocalBreathiness,
15970
+ ten: t.vocalTension,
15848
15971
  uni: t.vocalOctaveUnison
15849
15972
  })).filter(
15850
15973
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
@@ -15859,6 +15982,7 @@ var mountDAW = (target, options = {}) => {
15859
15982
  x2.del === 0 ? "" : `e${x2.del}`,
15860
15983
  x2.gen === 50 ? "" : `g${x2.gen}`,
15861
15984
  x2.bre === 50 ? "" : `h${x2.bre}`,
15985
+ x2.ten === 50 ? "" : `t${x2.ten}`,
15862
15986
  OCTAVE_UNISON_TOKEN[x2.uni]
15863
15987
  ].filter((s) => s.length > 0).join(" ");
15864
15988
  const head = params ? `${x2.model} ${params}` : x2.model;
@@ -16101,6 +16225,7 @@ var mountDAW = (target, options = {}) => {
16101
16225
  active.vocalDelay = 0;
16102
16226
  active.vocalGender = 50;
16103
16227
  active.vocalBreathiness = 50;
16228
+ active.vocalTension = 50;
16104
16229
  active.vocalOctaveUnison = "none";
16105
16230
  } else {
16106
16231
  for (const t of trackStates) {
@@ -16115,6 +16240,7 @@ var mountDAW = (target, options = {}) => {
16115
16240
  t.vocalDelay = 0;
16116
16241
  t.vocalGender = 50;
16117
16242
  t.vocalBreathiness = 50;
16243
+ t.vocalTension = 50;
16118
16244
  t.vocalOctaveUnison = "none";
16119
16245
  }
16120
16246
  }
@@ -16133,6 +16259,8 @@ var mountDAW = (target, options = {}) => {
16133
16259
  t.vocalDelay = lt.delay ?? 0;
16134
16260
  t.vocalGender = lt.gender ?? 50;
16135
16261
  t.vocalBreathiness = lt.breathiness ?? 50;
16262
+ t.vocalTension = lt.tension ?? 50;
16263
+ t.vocalTension = lt.tension ?? 50;
16136
16264
  t.vocalOctaveUnison = lt.octaveUnison ?? "none";
16137
16265
  });
16138
16266
  for (const p of placements) {
@@ -16217,6 +16345,7 @@ var mountDAW = (target, options = {}) => {
16217
16345
  active.vocalDelay = 0;
16218
16346
  active.vocalGender = 50;
16219
16347
  active.vocalBreathiness = 50;
16348
+ active.vocalTension = 50;
16220
16349
  active.vocalOctaveUnison = "none";
16221
16350
  } else {
16222
16351
  clearAll();
@@ -16233,6 +16362,7 @@ var mountDAW = (target, options = {}) => {
16233
16362
  t.vocalDelay = 0;
16234
16363
  t.vocalGender = 50;
16235
16364
  t.vocalBreathiness = 50;
16365
+ t.vocalTension = 50;
16236
16366
  t.vocalOctaveUnison = "none";
16237
16367
  }
16238
16368
  }
@@ -16539,7 +16669,7 @@ var mountDAW = (target, options = {}) => {
16539
16669
  }
16540
16670
  if (t.lyricModel) {
16541
16671
  t.vocalVibrato = true;
16542
- t.vocalReverb = isMainVocal ? 15 : 30;
16672
+ t.vocalReverb = isMainVocal ? 25 : 45;
16543
16673
  fireLyricsChange(t);
16544
16674
  }
16545
16675
  }
@@ -17597,6 +17727,7 @@ var mountDAW = (target, options = {}) => {
17597
17727
  t.vocalDelay = data.vocalDelay ?? 0;
17598
17728
  t.vocalGender = data.vocalGender ?? 50;
17599
17729
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17730
+ t.vocalTension = data.vocalTension ?? 50;
17600
17731
  t.vocalOctaveUnison = data.vocalOctaveUnison ?? "none";
17601
17732
  },
17602
17733
  applyTrackInstrument: (trackIndex, instrumentName) => {
@@ -17719,6 +17850,7 @@ var playSingingMML = async (mml, options = {}) => {
17719
17850
  delaySend: (lt.delay ?? 0) / 100,
17720
17851
  gender: (lt.gender ?? 50) / 100,
17721
17852
  breathiness: (lt.breathiness ?? 50) / 100,
17853
+ tension: (lt.tension ?? 50) / 100,
17722
17854
  octaveUnison: lt.octaveUnison,
17723
17855
  notes
17724
17856
  };
@@ -18531,6 +18663,11 @@ var createDtmStudio = async (options = {}) => {
18531
18663
  presetUI: true,
18532
18664
  ...options.features
18533
18665
  };
18666
+ const resolveDrumPatterns = (custom) => ({
18667
+ ...DRUM_PATTERNS,
18668
+ ...SONG_DRUM_PATTERNS,
18669
+ ...normalizeDrumPatterns(custom ?? {})
18670
+ });
18534
18671
  const audioCtx = options.audioContext ?? new AudioContext({ sampleRate: 44100 });
18535
18672
  const masterGain = audioCtx.createGain();
18536
18673
  masterGain.gain.value = options.masterVolume ?? 1;
@@ -19192,7 +19329,7 @@ var createDtmStudio = async (options = {}) => {
19192
19329
  await loadRequiredDrums(
19193
19330
  getDrumPatternKeys(
19194
19331
  meta.drum,
19195
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19332
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
19196
19333
  ),
19197
19334
  meta.drumFont || "FluidR3_GM_sf2_file:0"
19198
19335
  );
@@ -19283,7 +19420,7 @@ var createDtmStudio = async (options = {}) => {
19283
19420
  await loadRequiredDrums(
19284
19421
  getDrumPatternKeys(
19285
19422
  meta.drum,
19286
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19423
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
19287
19424
  )
19288
19425
  );
19289
19426
  }
@@ -19363,7 +19500,7 @@ var createDtmStudio = async (options = {}) => {
19363
19500
  await loadRequiredDrums(
19364
19501
  getDrumPatternKeys(
19365
19502
  meta.drum,
19366
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19503
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
19367
19504
  )
19368
19505
  );
19369
19506
  }
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) {
@@ -3748,8 +3831,7 @@ var createReverbImpulse = (ctx, decaySec = DEFAULT_REVERB_DECAY_SEC) => {
3748
3831
  }
3749
3832
  return impulse;
3750
3833
  };
3751
- var REVERB_MAX_WET = 0.6;
3752
- var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100 * REVERB_MAX_WET;
3834
+ var reverbAmountToGain = (amount) => Math.max(0, Math.min(100, amount)) / 100;
3753
3835
 
3754
3836
  // src/sequencer.ts
3755
3837
  var STEPS_PER_BEAT = 48;
@@ -9442,6 +9524,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
9442
9524
  delaySend: (lt.delay ?? 0) / 100,
9443
9525
  gender: (lt.gender ?? 50) / 100,
9444
9526
  breathiness: (lt.breathiness ?? 50) / 100,
9527
+ tension: (lt.tension ?? 50) / 100,
9445
9528
  octaveUnison: lt.octaveUnison,
9446
9529
  notes
9447
9530
  };
@@ -13292,6 +13375,16 @@ var BREATHINESS_INFO_HTML = `
13292
13375
  <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>
13293
13376
  </div>
13294
13377
  `;
13378
+ var TENSION_INFO_HTML = `
13379
+ <div class="dtm-modal-body-content">
13380
+ <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
13381
+ <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>
13382
+ <h4>\u30D6\u30EC\u30B7\u30CD\u30B9\u3068\u306E\u9055\u3044</h4>
13383
+ <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>
13384
+ <h4>\u4ED6\u306E\u8A2D\u5B9A\u3068\u306E\u517C\u306D\u5408\u3044</h4>
13385
+ <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>
13386
+ </div>
13387
+ `;
13295
13388
  var LYRIC_REVERB_INFO_HTML = `
13296
13389
  <div class="dtm-modal-body-content">
13297
13390
  <h4>\u4F55\u3092\u3059\u308B\u8A2D\u5B9A\u304B</h4>
@@ -13816,6 +13909,7 @@ var mountDAW = (target, options = {}) => {
13816
13909
  vocalDelay: t.vocalDelay,
13817
13910
  vocalGender: t.vocalGender,
13818
13911
  vocalBreathiness: t.vocalBreathiness,
13912
+ vocalTension: t.vocalTension,
13819
13913
  vocalOctaveUnison: t.vocalOctaveUnison
13820
13914
  };
13821
13915
  if (lyricsDebounceTimer) clearTimeout(lyricsDebounceTimer);
@@ -13881,6 +13975,7 @@ var mountDAW = (target, options = {}) => {
13881
13975
  vocalDelay: 0,
13882
13976
  vocalGender: 50,
13883
13977
  vocalBreathiness: 50,
13978
+ vocalTension: 50,
13884
13979
  vocalOctaveUnison: "none",
13885
13980
  trackInstrument: "",
13886
13981
  trackCompression: 0,
@@ -13912,6 +14007,7 @@ var mountDAW = (target, options = {}) => {
13912
14007
  delay: t.vocalDelay,
13913
14008
  gender: t.vocalGender,
13914
14009
  breathiness: t.vocalBreathiness,
14010
+ tension: t.vocalTension,
13915
14011
  octaveUnison: t.vocalOctaveUnison,
13916
14012
  syllables
13917
14013
  });
@@ -14722,6 +14818,7 @@ var mountDAW = (target, options = {}) => {
14722
14818
  delaySend: (lt.delay ?? 0) / 100,
14723
14819
  gender: (lt.gender ?? 50) / 100,
14724
14820
  breathiness: (lt.breathiness ?? 50) / 100,
14821
+ tension: (lt.tension ?? 50) / 100,
14725
14822
  octaveUnison: lt.octaveUnison,
14726
14823
  notes
14727
14824
  };
@@ -15150,6 +15247,12 @@ var mountDAW = (target, options = {}) => {
15150
15247
  <span class="dtm-label" data-dtm="lyric-breathiness-label"></span>
15151
15248
  <button class="dtm-infobtn" data-dtm="lyric-breathiness-info" title="\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15152
15249
  </div>
15250
+ <div class="dtm-row">
15251
+ <span class="dtm-label">\u30C6\u30F3\u30B7\u30E7\u30F3</span>
15252
+ <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">
15253
+ <span class="dtm-label" data-dtm="lyric-tension-label"></span>
15254
+ <button class="dtm-infobtn" data-dtm="lyric-tension-info" title="\u30C6\u30F3\u30B7\u30E7\u30F3\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
15255
+ </div>
15153
15256
  <div class="dtm-row">
15154
15257
  <span class="dtm-label" style="display:inline-flex;align-items:center;gap:2px">
15155
15258
  <input type="checkbox" data-dtm="lyric-vibrato" aria-label="\u81EA\u52D5\u30D3\u30D6\u30E9\u30FC\u30C8">\u30D3\u30D6\u30E9\u30FC\u30C8
@@ -15253,6 +15356,18 @@ var mountDAW = (target, options = {}) => {
15253
15356
  lyricBreathinessInfo.addEventListener("click", () => {
15254
15357
  showModal("\u30D6\u30EC\u30B7\u30CD\u30B9\u306E\u89E3\u8AAC", BREATHINESS_INFO_HTML);
15255
15358
  });
15359
+ const lyricTension = lyricDiv.querySelector(
15360
+ '[data-dtm="lyric-tension"]'
15361
+ );
15362
+ const lyricTensionLabel = lyricDiv.querySelector(
15363
+ '[data-dtm="lyric-tension-label"]'
15364
+ );
15365
+ const lyricTensionInfo = lyricDiv.querySelector(
15366
+ '[data-dtm="lyric-tension-info"]'
15367
+ );
15368
+ lyricTensionInfo.addEventListener("click", () => {
15369
+ showModal("\u30C6\u30F3\u30B7\u30E7\u30F3\u306E\u89E3\u8AAC", TENSION_INFO_HTML);
15370
+ });
15256
15371
  const lyricVibrato = lyricDiv.querySelector(
15257
15372
  '[data-dtm="lyric-vibrato"]'
15258
15373
  );
@@ -15340,6 +15455,8 @@ var mountDAW = (target, options = {}) => {
15340
15455
  lyricGenderLabel.textContent = `${active.vocalGender}`;
15341
15456
  lyricBreathiness.value = String(active.vocalBreathiness);
15342
15457
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
15458
+ lyricTension.value = String(active.vocalTension);
15459
+ lyricTensionLabel.textContent = `${active.vocalTension}`;
15343
15460
  lyricVibrato.checked = active.vocalVibrato;
15344
15461
  lyricOctaveUnison.value = active.vocalOctaveUnison;
15345
15462
  const updateLyricCount = () => {
@@ -15510,6 +15627,11 @@ var mountDAW = (target, options = {}) => {
15510
15627
  lyricBreathinessLabel.textContent = `${active.vocalBreathiness}`;
15511
15628
  fireLyricsChange(active);
15512
15629
  });
15630
+ lyricTension.addEventListener("input", () => {
15631
+ active.vocalTension = Number.parseInt(lyricTension.value, 10);
15632
+ lyricTensionLabel.textContent = `${active.vocalTension}`;
15633
+ fireLyricsChange(active);
15634
+ });
15513
15635
  }
15514
15636
  if (active.config.id === "chord" && showChord) {
15515
15637
  const div = document.createElement("div");
@@ -15714,6 +15836,7 @@ var mountDAW = (target, options = {}) => {
15714
15836
  del: t.vocalDelay,
15715
15837
  gen: t.vocalGender,
15716
15838
  bre: t.vocalBreathiness,
15839
+ ten: t.vocalTension,
15717
15840
  uni: t.vocalOctaveUnison
15718
15841
  })).filter(
15719
15842
  (x2) => x2.model.length > 0 && x2.text.length > 0 && x2.notes.length > 0
@@ -15728,6 +15851,7 @@ var mountDAW = (target, options = {}) => {
15728
15851
  x2.del === 0 ? "" : `e${x2.del}`,
15729
15852
  x2.gen === 50 ? "" : `g${x2.gen}`,
15730
15853
  x2.bre === 50 ? "" : `h${x2.bre}`,
15854
+ x2.ten === 50 ? "" : `t${x2.ten}`,
15731
15855
  OCTAVE_UNISON_TOKEN[x2.uni]
15732
15856
  ].filter((s) => s.length > 0).join(" ");
15733
15857
  const head = params ? `${x2.model} ${params}` : x2.model;
@@ -15970,6 +16094,7 @@ var mountDAW = (target, options = {}) => {
15970
16094
  active.vocalDelay = 0;
15971
16095
  active.vocalGender = 50;
15972
16096
  active.vocalBreathiness = 50;
16097
+ active.vocalTension = 50;
15973
16098
  active.vocalOctaveUnison = "none";
15974
16099
  } else {
15975
16100
  for (const t of trackStates) {
@@ -15984,6 +16109,7 @@ var mountDAW = (target, options = {}) => {
15984
16109
  t.vocalDelay = 0;
15985
16110
  t.vocalGender = 50;
15986
16111
  t.vocalBreathiness = 50;
16112
+ t.vocalTension = 50;
15987
16113
  t.vocalOctaveUnison = "none";
15988
16114
  }
15989
16115
  }
@@ -16002,6 +16128,8 @@ var mountDAW = (target, options = {}) => {
16002
16128
  t.vocalDelay = lt.delay ?? 0;
16003
16129
  t.vocalGender = lt.gender ?? 50;
16004
16130
  t.vocalBreathiness = lt.breathiness ?? 50;
16131
+ t.vocalTension = lt.tension ?? 50;
16132
+ t.vocalTension = lt.tension ?? 50;
16005
16133
  t.vocalOctaveUnison = lt.octaveUnison ?? "none";
16006
16134
  });
16007
16135
  for (const p of placements) {
@@ -16086,6 +16214,7 @@ var mountDAW = (target, options = {}) => {
16086
16214
  active.vocalDelay = 0;
16087
16215
  active.vocalGender = 50;
16088
16216
  active.vocalBreathiness = 50;
16217
+ active.vocalTension = 50;
16089
16218
  active.vocalOctaveUnison = "none";
16090
16219
  } else {
16091
16220
  clearAll();
@@ -16102,6 +16231,7 @@ var mountDAW = (target, options = {}) => {
16102
16231
  t.vocalDelay = 0;
16103
16232
  t.vocalGender = 50;
16104
16233
  t.vocalBreathiness = 50;
16234
+ t.vocalTension = 50;
16105
16235
  t.vocalOctaveUnison = "none";
16106
16236
  }
16107
16237
  }
@@ -16408,7 +16538,7 @@ var mountDAW = (target, options = {}) => {
16408
16538
  }
16409
16539
  if (t.lyricModel) {
16410
16540
  t.vocalVibrato = true;
16411
- t.vocalReverb = isMainVocal ? 15 : 30;
16541
+ t.vocalReverb = isMainVocal ? 25 : 45;
16412
16542
  fireLyricsChange(t);
16413
16543
  }
16414
16544
  }
@@ -17466,6 +17596,7 @@ var mountDAW = (target, options = {}) => {
17466
17596
  t.vocalDelay = data.vocalDelay ?? 0;
17467
17597
  t.vocalGender = data.vocalGender ?? 50;
17468
17598
  t.vocalBreathiness = data.vocalBreathiness ?? 50;
17599
+ t.vocalTension = data.vocalTension ?? 50;
17469
17600
  t.vocalOctaveUnison = data.vocalOctaveUnison ?? "none";
17470
17601
  },
17471
17602
  applyTrackInstrument: (trackIndex, instrumentName) => {
@@ -17588,6 +17719,7 @@ var playSingingMML = async (mml, options = {}) => {
17588
17719
  delaySend: (lt.delay ?? 0) / 100,
17589
17720
  gender: (lt.gender ?? 50) / 100,
17590
17721
  breathiness: (lt.breathiness ?? 50) / 100,
17722
+ tension: (lt.tension ?? 50) / 100,
17591
17723
  octaveUnison: lt.octaveUnison,
17592
17724
  notes
17593
17725
  };
@@ -18399,6 +18531,11 @@ var createDtmStudio = async (options = {}) => {
18399
18531
  presetUI: true,
18400
18532
  ...options.features
18401
18533
  };
18534
+ const resolveDrumPatterns = (custom) => ({
18535
+ ...DRUM_PATTERNS,
18536
+ ...SONG_DRUM_PATTERNS,
18537
+ ...normalizeDrumPatterns(custom ?? {})
18538
+ });
18402
18539
  const audioCtx = options.audioContext ?? new AudioContext({ sampleRate: 44100 });
18403
18540
  const masterGain = audioCtx.createGain();
18404
18541
  masterGain.gain.value = options.masterVolume ?? 1;
@@ -19060,7 +19197,7 @@ var createDtmStudio = async (options = {}) => {
19060
19197
  await loadRequiredDrums(
19061
19198
  getDrumPatternKeys(
19062
19199
  meta.drum,
19063
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19200
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
19064
19201
  ),
19065
19202
  meta.drumFont || "FluidR3_GM_sf2_file:0"
19066
19203
  );
@@ -19151,7 +19288,7 @@ var createDtmStudio = async (options = {}) => {
19151
19288
  await loadRequiredDrums(
19152
19289
  getDrumPatternKeys(
19153
19290
  meta.drum,
19154
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19291
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
19155
19292
  )
19156
19293
  );
19157
19294
  }
@@ -19231,7 +19368,7 @@ var createDtmStudio = async (options = {}) => {
19231
19368
  await loadRequiredDrums(
19232
19369
  getDrumPatternKeys(
19233
19370
  meta.drum,
19234
- opts.drumPatterns ?? options.drumPatterns ?? DRUM_PATTERNS
19371
+ resolveDrumPatterns(opts.drumPatterns ?? options.drumPatterns)
19235
19372
  )
19236
19373
  );
19237
19374
  }
@@ -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.89",
5
+ "version": "0.1.91",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {