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_SEC | 2.5 | 0.5 | −2.0s |
PRE_POST_PADDING_PACKETS | 75(1.5s) | 85(1.7s) | +0.2s |
NOISE_LEAD_PACKETS | 65(1.3s) | 5(0.1s) | −1.2s |
NOISE_FILL_AMP | 150 | 150 | 据え置き |
PRE_AUDIO_SILENCE_SEC | 0.0 | 0.0 | 据え置き |
GAP_AFTER_INTRO_SEC | 0.5 | 0.5 | 据え置き |
効果(実測 / No.7 話者・JI2TAB 応答)
| 開始時 | 最終 | |
|---|---|---|
| カーチャンク受信終了 → intro 開始 | 4.02s | 2.22s |
| カーチャンク受信終了 → 送信終了 | 15.26s | 13.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 固定。
| 値 | 秒 | 頭欠け |
|---|---|---|
| 35 | 0.7s | 出る |
| 75 | 1.5s | 出る(3回中2回) |
| 85 | 1.7s | 出ない ← 採用 |
| 100 | 2.0s | 出ない |
床は 1.5〜1.7s の間。 75(1.5s)は V1.88 以降ずっとこの値だったため、頭欠けは長期間発生し続けていたことになる。
この値は前後で共有されるため、後パディング(送信終端側の無音)も同じ長さになる。増やすと intro 開始がそのぶん遅れる。
2.3 NOISE_LEAD_PACKETS(前パディングのうち先頭からトーンにする個数)
| 値 | 秒 | 前パディング | 頭欠け | ブーン |
|---|---|---|---|---|
| 5 | 0.1s | 1.7s | なし | なし ← 採用 |
| 10 | 0.2s | 1.7s | なし | なし |
| 10 | 0.2s | 0.7s | 出る | — |
| 65 | 1.3s | 1.5s | 出る | なし |
| 85 | 1.7s | 1.7s | なし | 1秒弱 |
| 90 | 1.8s | 2.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(トーン振幅)
| 値 | dBFS | TGIF に食われる時間 | 聞こえ方 |
|---|---|---|---|
| 0 | 無音 | 3.06s(V1.84 実測) | — |
| 150 | −47 | 1.5〜1.8s | ブーン聞こえず ← 採用(据え置き) |
| 2500 | −22 | 0.3〜0.5s | ブーン 1.0s → プツッ → 0.5s(大きすぎ) |
振幅を上げるとゲートは 3〜5 倍速く開く。 V1.85 の見立て(エネルギー積算型/音量が大きいほど早く開く)自体は正しかった。ただし食われ残ったトーンがそのまま「ブーン」として耳に届くため、前パディングの大幅短縮とセットでなければ使えない。150 は「AMBE 非無音判定の実証値」であり、これ以上下げると AMBE が無音として符号化してヘッダごと捨てられる。
2.5 話者(wav_source.json の voice)
| 話者 | 結果 |
|---|---|
| 四国めたん(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 刻み RMS | middle 区間は −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_SEC | 0.5s(0 と 0.1 では欠ける) |
| TGIF のゲートが開く前に intro が始まる | PRE_POST_PADDING_PACKETS | 1.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 地点)まで食う | 時間的にありえない |
| 3 | gap 0.5s でゲートが再び閉じ、コールサイン頭を食う | ファイルを直接再生しても鳴っていた |
| 4 | sox のリサンプルでクリップ | full-scale=0 / max-run=0 |
| 5 | intro と middle の継ぎ目に段差 | サンプル値が連続していた |
| 6 | max-jump/peak 1.73 = ナイキスト成分 = エイリアシング | スペクトル重心 1300/1421/1300 |
| 7 | 3ファイル分割で concat を消せば速くなる | sox = 10ms |
| 8 | VOICEVOX の合成時間が 1.6〜3.2s と不安定 | ベンチが汚染されていた(2つ目の ONNX ランタイム)。実際は 0.9s |
| 9 | V1.96 の片肺配備(vv_say.py が 48k のまま) | grep で否定(両経路とも撤去済み) |
| 10 | 後パディングが md380-emu の消化時間を稼いでいた | DroidStar の損失は受信側・送信中でない |
| 11 | 頭欠けは lead と無関係 | 定時放送が無傷という事実 |
| 12 | TGIF のゲートは 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.7s | 0.2s | 出る |
| 1.5s | 1.3s | 出る |
| 1.7s | 0.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_PACKETS | V1.88 の「頭欠けが出る場合は 75(全区間ノイズ)に戻す」は誤りと明記。実測3点を併記 |
REPLY_TX_LEAD_DELAY_SEC | V1.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 に戻す。その戻しが頭欠けを再発させる。