0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Irodori-TTSの感情に強さのつまみを付ける — 絵文字の重ねがけ回数とテキストCFGの写像

0
Posted at

この記事は Zenn に公開した記事の再掲です(原文・最新版: https://zenn.dev/acs_developer/articles/irodori-tts-emotion-intensity-emoji-cfg )。

当方では、日本語TTSの Irodori-TTS(500M v3)をバックエンドにした自作の合成WebUIで、台本をブロック単位に分けて読み上げています。困っていたのは感情プリセットが強すぎることでした。「ささやき」を選ぶとほぼ息だけになり、「嬉しい」も一段上の芝居になってしまいます。ほしいのは「少しだけ明るく」です。

そこで各ブロックに 強度 0.0〜1.5 のスライダーを付けました。本稿では、Irodori-TTSで感情が何によって効いているかをコードで確かめた結果と、それを連続値へ写像した式、写像を全域でスイープしたときに見えた「階段」についてまとめます。

感情は「絵文字の回数」と「テキストCFG」で効いていた

Irodori-TTSは、本文の先頭に置いた絵文字をスタイルの指示として読みます。公式のTipsには「同じ絵文字を重ねると効果が強まる」とあります。当方のWebUIは、感情ラベルを次の2つへ変換して推論に渡していました。

  1. 本文の先頭に、ラベルに対応する絵文字を repeat 回つける
  2. cfg_scale_text(テキスト条件のCFG)を上げる

推論の呼び出し部分は次のとおりです。

from irodori_tts.inference_runtime import SamplingRequest

request = SamplingRequest(
    text=text,                 # 例: "😊😊出た。言わないやつ。"
    ref_wav=ref_wav_path,      # 話者の参照音声
    num_steps=num_steps,
    cfg_scale_text=cfg_scale_text,
    cfg_scale_speaker=cfg_scale_speaker,
    seed=seed,                 # 当方は未指定(None)
)

ラベル定義ファイル(JSON)にあった既定値は次のとおりです。

種類 絵文字×回数 cfg_scale_text cfg_scale_speaker
ニュートラル なし 3.0 5.0
感情つき(既定) ×2 4.5 5.0
ささやき 👂×3 5.0 4.0

実装コメントによると、テキストCFGを3.0のままにすると話者CFG(5.0)が勝って感情が乗りにくいため、感情つきのときは4.5へ上げていました。つまり強さのつまみは最初から2本あったわけです(回数とCFG)。UIから触れなかっただけでした。

写像式:強度 i を回数と2つのCFGへ

ラベルの既定値を「強度1.0」とみなし、0.0をニュートラル、1.5を「既定より強め」に置きました。ラベルの回数を R、テキストCFGを T、話者CFGを S とします。

NEUTRAL_CFG_TEXT, NEUTRAL_CFG_SPEAKER = 3.0, 5.0
CFG_TEXT_MAX, CFG_SPEAKER_MIN, REPEAT_MAX = 6.0, 3.5, 4

def apply_emotion(emoji, R, T, S, text, i):
    i = max(0.0, min(1.5, float(i)))
    if not emoji or abs(i - 1.0) < 1e-6:
        return preset_path(...)          # 1.0は従来の経路をそのまま通す
    cfg_text = min(CFG_TEXT_MAX, 3.0 + (T - 3.0) * i)
    cfg_speaker = max(CFG_SPEAKER_MIN, 5.0 + (S - 5.0) * i)
    if i <= 0.0:
        rep = 0
    elif i > 1.0:
        rep = min(R + 1, REPEAT_MAX)
    else:
        rep = max(1, round(R * i))
    return emoji * rep + text, cfg_text, cfg_speaker

設計で決めたことは3つです。

  • i=1.0 のときは写像を通さず、従来の関数の出力をそのまま使う。既存の767ブロックは列の既定値を1.0にしたため、パラメータが1ビットも変わりません。強度を渡さない古い呼び出し元も1.0として動きます
  • CFGは線形に動かします。ただし上限と下限で頭打ちにします(text≤6.0、speaker≥3.5)
  • 絵文字は整数なので丸めます。0より大きければ最低1回はつけます

全域スイープで見えた「階段」

UIの刻み(0.05)で、本番と同じ apply_emotion を 0.00〜1.50 の全31点に当てました。関数は本番のソースからASTで切り出して実行しています。抜粋は次のとおりです。

## 明るい・嬉しい (R=2, T=4.5, S=5.0)
i=0.00 repeat=0 cfg_text=3.000
i=0.05 repeat=1 cfg_text=3.075   <- repeat変化
i=0.70 repeat=1 cfg_text=4.050
i=0.75 repeat=2 cfg_text=4.125   <- repeat変化
i=1.00 repeat=2 cfg_text=4.500   preset=True
i=1.05 repeat=3 cfg_text=4.575   <- repeat変化
i=1.50 repeat=3 cfg_text=5.250

## ささやき (R=3, T=5.0, S=4.0)
i=0.05 repeat=1 cfg_text=3.100 cfg_speaker=4.950
i=0.50 repeat=2 cfg_text=4.000 cfg_speaker=4.500   <- repeat変化
i=0.85 repeat=3 cfg_text=4.700 cfg_speaker=4.150   <- repeat変化
i=1.05 repeat=4 cfg_text=5.100 cfg_speaker=3.950   <- repeat変化
i=1.50 repeat=4 cfg_text=6.000 cfg_speaker=3.500

スライダーは連続値に見えますが、中身は絵文字回数の3〜4段の階段を、CFGが斜面で埋めている形でした。気づいた点は次のとおりです。

  • R=2 のラベルでは、0.05〜0.70 の14段階がすべて絵文字1回です。たとえば0.30と0.70の差はCFGの3.45と4.05だけです
  • 1.00→1.05 では、CFGが0.075しか動かないのに絵文字が1回増えます。入力の変化が最も大きい境目が「既定値のすぐ上」にあることになります。1.0より上を滑らかにしたいなら、R + 1 へ飛ばさず round(R * i) を延長するほうが素直です
  • Pythonの round() は偶数丸め(round(0.5)=0、round(2.5)=2)です。今回の刻みで同点になるのは R=2 の 0.25 と 0.75、R=3 の 0.50 だけでした。0.25 は max(1, …) で1回に戻ります。どの点も四捨五入と同じ結果になり、実害はありませんでした。ただし刻みを変えると結果がずれる箇所が出ます

合成した結果(1文・各1回の観測)

同じ1文(「出た。言わないやつ。」)を同じ話者で合成しました。パラメータは、ワーカーのログに出した実際の受け渡し値です。

感情 強度 絵文字 text CFG speaker CFG 尺 LUFS
明るい 0.00 0 3.00 5.0 4.04s -16.2
明るい 0.30 1 3.45 5.0 4.04s -16.2
明るい 0.60 1 3.90 5.0 4.04s -16.3
明るい 1.00 2 4.50 5.0 4.12s -15.7
明るい 1.30 3 4.95 5.0 4.20s -15.7
ささやき 0.30 1 3.60 4.7 4.04s -15.9
ささやき 0.70 2 4.40 4.3 4.16s -16.5
ささやき 1.00 3 5.00 4.0 4.32s -16.5
  • 評価者1名の聴取では、段階の差は聞き分けられました(どの値が実用的かは、まだ運用しながら調べている段階です)
  • 尺は、絵文字の回数が増えたところでわずかに伸びました(ささやき 4.04→4.16→4.32秒)
  • ささやきでもラウドネスは下がりませんでした(-15.9〜-16.5 LUFS)。ピークもどの本も0dBFS付近にそろっていました。音量で「小声らしさ」を判定する自動検査は使えません。声色で判断することになります
  • seed を指定していないので、同じパラメータでも毎回波形は変わります。1.0は「同じ設定」という意味で、「同じ音」が出るわけではありません。強度の比較は、1本ずつ聴いて決め打ちしないほうが安全です

なお、これは1文・各1回の観測です。ラベルや話者によって効き方は変わる可能性があります。

ログに「実際に渡した値」を残す

写像を挟むと、UIに出ている数字と推論に渡る数字が一致しなくなります。そこでワーカーは合成のたびに1行を出すようにしました。

[intensity] block=2181 take=b2181_….wav emotion=明るい・嬉しい i=0.30 repeat=1 cfg_text=3.45 cfg_speaker=5.0 preset=False

実装から3日間で、このログは135行たまりました。そのうち124行は「ニュートラル・i=1.00・preset=True」、つまり従来の経路そのままでした。既存の台本に影響が出ていないことは、この行で確認できます。

まとめ

  • Irodori-TTSの感情は「先頭の絵文字の回数」と「cfg_scale_text」の2本で効いている。強度のつまみは、この2本へ写像すれば作れる
  • 既定値(i=1.0)は写像を通さず、従来の経路をそのまま通す。こうすると既存データとの互換が崩れない
  • 整数の回数を丸めるので、連続値のスライダーでも中身は階段になる。全域をスイープし、段の位置(特に既定値の直上)を把握してから運用する
  • ラウドネスは強度の指標にならなかった。判定は耳で行い、実際に渡した値をログで追えるようにしておく

検証の続きや関連ツールはACS Developerで公開しています → ACS Developer

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?