OpenCCVoice for DVSwitch V1.98vv に至るまでの実験記録

期間: 2026-07-15 〜 07-17 対象: ocv-voicevox(Incus / 192.168.1.170 / 直 TGIF 接続 / V1.96 配備状態) 目的: カーチャンク応答の立ち上がり短縮


1. 結論

最終設定

定数開始時最終変化
REPLY_TX_LEAD_DELAY_SEC2.50.5−2.0s
PRE_POST_PADDING_PACKETS75(1.5s)85(1.7s)+0.2s
NOISE_LEAD_PACKETS65(1.3s)5(0.1s)−1.2s
NOISE_FILL_AMP150150据え置き
PRE_AUDIO_SILENCE_SEC0.00.0据え置き
GAP_AFTER_INTRO_SEC0.50.5据え置き

効果(実測 / No.7 話者・JI2TAB 応答)

開始時最終
カーチャンク受信終了 → intro 開始4.02s2.22s
カーチャンク受信終了 → 送信終了15.26s13.9s
頭欠け出る(3回中2回)出ない
ブーン(トーンの聞こえ残り)聞こえず聞こえず

1.8 秒速くなり、同時に頭欠けも消えた。 頭欠けは今回の実験で発生したものではなく、開始時点から出ていた(速くしたから壊れたのではない)。


2. 試した項目と結果

2.1 REPLY_TX_LEAD_DELAY_SEC(受信終了後、送信せずに待つ秒数)

前パディング 85(1.7s)固定で床を挟み込んだ。

頭欠け
0出る
0.1出る
0.5出ない ← 採用
2.5出ない(ただし 2.0s 遅い)

床は 0.1〜0.5 の間。 0.1〜0.5 の間を詰める余地は残っている。

なお開始状態(lead 2.5 / 前パディング 1.5s)では頭欠けが出ていた。lead を大きくしても、前パディングが足りなければ直らない。

2.2 PRE_POST_PADDING_PACKETS(前パディング全体の長さ / 前後共有)

lead 0.5 固定。

頭欠け
350.7s出る
751.5s出る(3回中2回)
851.7s出ない ← 採用
1002.0s出ない

床は 1.5〜1.7s の間。 75(1.5s)は V1.88 以降ずっとこの値だったため、頭欠けは長期間発生し続けていたことになる。

この値は前後で共有されるため、後パディング(送信終端側の無音)も同じ長さになる。増やすと intro 開始がそのぶん遅れる。

2.3 NOISE_LEAD_PACKETS(前パディングのうち先頭からトーンにする個数)

前パディング頭欠けブーン
50.1s1.7sなしなし ← 採用
100.2s1.7sなしなし
100.2s0.7s出る
651.3s1.5s出るなし
851.7s1.7sなし1秒弱
901.8s2.0sなし一瞬

トーンを短くするほどブーンが消え、頭欠けも出ない。 V1.88 のコメント(「ゲート床 1.0〜1.3s を跨ぐ最小限」「頭欠けが出る場合は 75=全区間ノイズに戻す」)とは逆の結果

採用値 5 は len(_FADE_STEPS) == 5 と等しいため、_lead_block() の仕様上5個すべてがフェード(振幅 112→82→52→30→12)になり、振幅 150 の定常トーンは1ブロックも送出されない。それでもゲートは開く。5 未満は未検証。

2.4 NOISE_FILL_AMP(トーン振幅)

dBFSTGIF に食われる時間聞こえ方
0無音3.06s(V1.84 実測)
150−471.5〜1.8sブーン聞こえず ← 採用(据え置き)
2500−220.3〜0.5sブーン 1.0s → プツッ → 0.5s(大きすぎ)

振幅を上げるとゲートは 3〜5 倍速く開く。 V1.85 の見立て(エネルギー積算型/音量が大きいほど早く開く)自体は正しかった。ただし食われ残ったトーンがそのまま「ブーン」として耳に届くため、前パディングの大幅短縮とセットでなければ使えない。150 は「AMBE 非無音判定の実証値」であり、これ以上下げると AMBE が無音として符号化してヘッダごと捨てられる。

2.5 話者(wav_source.jsonvoice

話者結果
四国めたん(style_id=2 / 0.vvm)「ティーエー」の エーヘィ に化ける
No.7 アナウンス(style_id=30 / 6.vvm)解消 ← 採用

音声の欠陥ではなく話者固有の癖だった(§3)。

2.6 見送った項目

項目理由
PRE_AUDIO_SILENCE_SEC(実音声前の無音パッド)同じ効果なら REPLY_TX_LEAD_DELAY_SEC の方がチャネル占有が 2 秒短い(lead は送信しない/head_silence は送信しながら黙る)
GATE_BURST_PACKETS定義のみで送出ループから未参照。値を変えても何も起きない
middle と outro の統合(局のカーチャンクです を1単位で合成)韻律は素直になるが、頭欠けの解決とは無関係。fixed_outro.wav が不要になる利点はある
全文動的合成fixed_intro.wav は第30条の識別信号で必須。全文動的にすると識別信号側と文言がズレる
キャッシュ構造の分割・重複排除sox は合計 10ms。削っても 6ms。速度に効かない(§4.5)

3. 「7秒目のモゴ」— 話者の癖だった

実験途中で「動的コールサインの音声が欠ける」との報告。/dev/shm のキャッシュを /tmp/JI2TAB.wav(161,984B = 10.121s)にコピーして解析した。

測定結果
構造intro 6.443s / gap 0.500s / middle 2.079s / outro 1.099s = 10.121s ✓
200ms 刻み RMSmiddle 区間は −22〜−30dB で埋まっている(欠落なし)
ピーク / クリップmiddle peak 8262(−12.0dB)/ full-scale 0 個 / max-run 0
継ぎ目gap 末尾 (0,-1,0,0) → middle 先頭 (0,0,0,0,0,0,0,-1)(段差なし)
等価スペクトル重心intro 1300Hz / middle 1421Hz / outro 1300Hz(差 9%)

5つの測定がすべて「ファイルは正常」と答えた。 最終的に、ファイルを直接再生して「ティーエー」の エーヘィ に聞こえることが判明。四国めたん固有の癖であって、パイプラインの欠陥ではなかった。話者を No.7 に変えて解消。


4. 分かったこと

4.1 頭欠けの原因は2つあり、独立していた

原因対策する定数必要量
受信の残処理が空く前にキーアップするREPLY_TX_LEAD_DELAY_SEC0.5s(0 と 0.1 では欠ける)
TGIF のゲートが開く前に intro が始まるPRE_POST_PADDING_PACKETS1.7s(0.7s と 1.5s では欠ける)

両方必要。 どちらか一方では直らない。

4.2 決定的な手がかりは「定時放送は頭欠けしない」だった

定時放送(001/002・時報)と カーチャンク応答は、冒頭の音声も送出コードも前パディングも完全に同一。違いは「直前に受信があったか」だけ。定時放送が無傷であることが、REPLY_TX_LEAD_DELAY_SEC が「受信の残処理待ち」であることの傍証になった。

V1.77 の当初の理屈 ──「経路が空く前に応答がキーアップするとイントロ頭が食われる」── は正しかった

4.3 REPLY_TX_LEAD_DELAY_SEC = 2.5 の根拠は失効していた

V1.95a のコメント曰く「キャッシュ OFF(毎回生成 ≒ 検知から約 2.4s 後の送出)では正常だったため、命中時のリードを生成時間相当まで広げて同じ送出タイミングに揃える」。

測定値ではなく、たまたま上手くいっていた合成時間を模倣した代理値だった。そして V1.96 の output_sampling_rate=48000 撤去で合成が高速化し、前提そのものが消えていた

実測: bot 自身の Precache ready ログ(ヘッダから 0.909s 後)から、合成は 0.9 秒。当時の 2.4s の 1/3 以下。

4.4 TGIF のゲートは 1.32 秒固定ではない

V1.85 は「−47dBFS ノイズでスキップが 3.06s→1.32s に短縮」と記録しているが、今回の実測では振幅 150 で 1.5〜1.8s(前パディング 2.0s でブーンが 1 秒弱聞こえたことから逆算)。V1.88 のコメントも「実測≈1.0〜1.3s」と幅を持たせており、もともと揺れる値。前パディング 1.5s で「3回中1回きれい・2回頭欠け」と割れたのは、この揺れがマージンを跨いだためと考えられる。

4.5 キャッシュ構造は速度に効かない

sox の実測: rate=0.004s / pad=0.003s / concat=0.003s = 合計 10ミリ秒

キャッシュ1件 161,984B のうち 128,712B(79%)は全コールサイン共通で、毎回同じ入力を再処理している ── のは事実だが、削っても 6ms しか浮かない。ストレージの話でしかない。


5. 外した仮説(13件)

#仮説何が殺したか
1制約は V(音声開始時刻)にあるV=2.03s 同一の3回で結果が割れた
2受信の残処理がコールサイン(6.9s 地点)まで食う時間的にありえない
3gap 0.5s でゲートが再び閉じ、コールサイン頭を食うファイルを直接再生しても鳴っていた
4sox のリサンプルでクリップfull-scale=0 / max-run=0
5intro と middle の継ぎ目に段差サンプル値が連続していた
6max-jump/peak 1.73 = ナイキスト成分 = エイリアシングスペクトル重心 1300/1421/1300
73ファイル分割で concat を消せば速くなるsox = 10ms
8VOICEVOX の合成時間が 1.6〜3.2s と不安定ベンチが汚染されていた(2つ目の ONNX ランタイム)。実際は 0.9s
9V1.96 の片肺配備(vv_say.py が 48k のまま)grep で否定(両経路とも撤去済み)
10後パディングが md380-emu の消化時間を稼いでいたDroidStar の損失は受信側送信中でない
11頭欠けは lead と無関係定時放送が無傷という事実
12TGIF のゲートは 1.32 秒固定amp 150 で 1.5〜1.8s、amp 2500 で 0.3〜0.5s
13エネルギー積算型だからトーンは長く必要noise=5(0.1s)で頭欠けなし

仮説 2〜6 はすべて「音を運ぶ側(配線・変換・電波)」を疑ったもの。実際の原因は「音を作る側(話者の癖)」だった。


6. 未解明のこと

6.1 ゲートの挙動モデル

前パディングトーン長頭欠け
0.7s0.2s出る
1.5s1.3s出る
1.7s0.1s出ない

トーンの長さでは説明がつかない。 1.3s のトーンで欠けて、0.1s のトーンで欠けない。効いているのは前パディングの合計だけに見える。しかも採用値では定常トーンが1ブロックも出ておらず、フェード(振幅 112→12)だけでゲートが開いている。末尾の 12 は「AMBE 非無音判定の実証値 150」を大きく下回る。

V1.85 の「エネルギー積算型」も V1.88 の「床を跨ぐ最小限」も、この結果を説明できない。理屈は分からないまま、実測で決めた値である。

6.2 残っている課題

  • プツッ音(「何かが切り替わっている感じ」)。定時放送にも出るのか未確認
  • 立ち上がり 1 秒以内(要望)に未到達。現在 t5 ≈ 2.2s。NOISE_FILL_AMP を上げてゲートを早く開かせ、前パディングを大幅短縮する道が残っている
  • DroidStar 経由の受信損失 32〜64%。送信中でないときに発生しており、送信設定とは無関係。WATCHDOG_MAX_LOSS_PCT のコメントに「実ログの watchdog ロス分布(31〜75%)」とあり、以前から知られた現象の可能性
  • 床の未詰め: lead は 0.1〜0.5 の間、前パディングは 1.5〜1.7s の間に、まだ余地がある
  • intro が 6.219s。応答全体 13.9s の 45%。文言短縮は最大の削り代だが、これは測定ではなく判断の問題
  • 死にコード: GATE_BURST_PACKETS / GATE_BURST_AMP(定義のみ・未参照)、_fill_if_silent()(V1.86 で呼び出しが外れた)。V1.98vv では【未使用】と注記のみ

7. V1.98vv での対応

コード(送出ロジック)は 1行も変更していない。定数3つと、実態と食い違っていたコメントの修正のみ。

箇所修正内容
NOISE_LEAD_PACKETSV1.88 の「頭欠けが出る場合は 75(全区間ノイズ)に戻す」は誤りと明記。実測3点を併記
REPLY_TX_LEAD_DELAY_SECV1.95a の根拠が V1.96 で失効していたことを明記。床の実測(0/0.1/0.5)を併記
PRE_POST_PADDING_PACKETS実測の根拠(0.7s/1.5s/1.7s)を併記。前後共有であることを明記
USRP_EOT_REPEAT「複数回送って確実化」だが既定値は 1 のまま、と訂正
GATE_BURST_*【未使用】(定義のみ・未参照)と明記
_fill_if_silent()【未使用】かつ、呼び戻しても完全一致判定のため効かないと明記
send_usrp_wav_with_padding() docstring「前後 1.5 秒」固定の誤記を定数参照に訂正

特に NOISE_LEAD_PACKETS = 5 の注記は必須。 V1.88 のコメントを読んだ人は 5 をバグと判断して 65 に戻す。その戻しが頭欠けを再発させる。

JJ2YYK
  • JJ2YYK

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です