遠隔収録で相手トラックに混入した自分の声(クロストーク)を消す:参照トラックの「揺らぎ込み時間合わせ」でRX De-bleedが効いた話

シリーズ: 遠隔収録の整音からYouTube公開まで(前編)

Riverside.fmで録った対談の相手トラックに、自分の声が入っていました(クロストーク)。収録したときにも、iZotope RXのDe-bleed(参照トラックを渡して漏れを消す機能)を試しています。公式マニュアルに従って2つのファイルの頭をぴったり合わせましたが、効きませんでした。やり方が悪いのだろうと思いつつ、マニュアル以外に手がかりが見つからず、やむなく漏れ(クロストーク)を残したまま編集して公開しました。

私にとって、とても思い入れの強い収録でした。それだけに、クロストークの入った版を出したことは、ずっと忸怩たる思いとして残っていました。

それから数年たち、AIによる音声処理がずいぶん進歩しました。今ならなんとかなるのではと、整音をやり直したのが今回です。

結果から言うと、最新の生成型モデルも、作り直された機械学習のDe-bleedも、この漏れは消せませんでした。効いたのは、収録当時に「効かない」と見切った昔ながらのDe-bleedに、時間ぴったりに合わせ直した参照を渡すことでした。AIが役に立ったのは、新しいモデルとしてではなく、130分の全長にわたって「ずれ」を測り、当時の失敗の原因を突き止める分析の相棒としてでした。この記事では、効かなかった3つの方法とその理由、効いた方法、そして同じ処理をPythonで自前実装してRXと同等まで持っていった話をまとめます。整音した音声を字幕・章付きのYouTube動画にするまでは、後編に書きました。

何が起きていたか

素材は2022年に録った約130分の対談です。相手は脳内言語化を研究しているまどろみさんで、収録にはRiverside.fmを使いました。Riversideは参加者それぞれの端末で録音するので、「自分のトラック」と「相手のトラック」が別のファイルで手に入ります。

問題は相手のトラックでした。相手が黙っている時間にも、私の声が遠くで小さく鳴っています。

どこから入ったのかは、実は分かっていません。最初はヘッドホンからの音漏れを疑いましたが、相手によると漏れてはいないはずとのことでした。確かなのは、私の声がネット経由で相手の端末に届き、何らかの経路で相手のトラックに混ざった、ということだけです。この記事では便宜上、この混入を「漏れ」と呼びます。

自分の声がネット経由で相手の端末に届き、経路不明のまま相手のトラックに混入する図

音声A: 相手トラックの元の音(漏れの実例)

1〜4秒あたりで小さく聞こえる男性の声(「怖くないですか」)が、漏れた私の声です。

単純な対策は、相手が黙っている所を下げるゲートです。実際、ゲートだけでも聴感はかなり良くなります。ただ、2人の声が重なった所ではゲートは開いたままなので、漏れも一緒に残ります。130分のうち、重なりは合計で約16分(947秒)ありました。ここをどう消すかが本題です。

効かなかった3つの方法

NLMSとは何か

最初に試したのはNLMS(正規化最小平均二乗)適応フィルタです。電話やWeb会議のエコーキャンセラーで昔から使われている、定番の方法です。

考え方は単純で、「参照(自分のクリーンな声)から、混ざった音を予測して引き算する」というものです。

  • 参照を、FIRフィルタ(今回は2048タップ、約46 ms分)に通す
  • その出力を「相手トラックに混ざった漏れの予測」として、相手トラックから引く
  • 引いた残り(誤差)が小さくなる方向へ、フィルタの係数を少しずつ更新し続ける
  • 「正規化(Normalized)」は、更新の大きさを参照の音量で割ることです。声の大小に関係なく、安定して学習できます

NLMSの仕組み:参照をフィルタに通して漏れを予測し、引き算した残りで係数を更新するループ

この方法には前提があります。漏れが参照の線形な変形であること、つまり「遅れ・音量・周波数特性(部屋や経路の響き)」の組み合わせで表せることです。

NLMSはなぜ効かなかったか

結果は、漏れが0.3 dB減っただけでした。重なった区間では、逆に4.7 dB増えていました。

原因は前提が崩れていたことです。今回の漏れは、少なくとも「私の声 → 私の端末のコーデック → ネット → 相手の端末」を通り、その先の経路も分かりません。何段もの非線形な処理を通っていることになります。測ってみると、漏れのうち参照から線形に予測できる成分は2割程度しかありませんでした。

追従を速くしようとステップサイズを上げると発散し、処理前より音が大きくなりました。発散しないように絞ると(ステップサイズ0.01、重なった区間では学習を止める)、今度はほとんど何も消えません。どちらに振っても効かない、という結果です。

DialogueSidonとは何か

次に試したのは、東京大学 猿渡研究室が公開している音声復元モデル「Sidon」の、2話者版のDialogueSidonです。

Sidonは、ノイズの乗った音声から特徴量を取り出して浄化し、そこから声を合成し直す生成型のモデルです。波形から雑音を削るのではありません。DialogueSidonは、その仕組みで混ざった会話を話者ごとに分け、それぞれを作り直して出力します。重なった区間でも、相手の声だけを取り出せるのではないかと期待しました。

音声B: 元 → DialogueSidon(各9秒)

前半が元の音、後半(10秒あたりから)がDialogueSidonの出力です。

漏れは消えました。ただ、相手の声が生成音の質感になり、いわゆる「ケロケロ」した声になりました。元の音との波形の相関は0.13で、聴いても別の声に聞こえます。

区間を選んで部分的に混ぜる案も考えましたが、問題は継ぎ目ではなく生成された声そのものです。採用はしませんでした。Sidonはもともと、音声合成の学習用データを大量にきれいにするための道具です。きれいだが本人の声ではなくなる、という性質は、対談の記録には向きませんでした。

De-bleedの新方式(参照なし)とは何か

3つ目は、iZotope RX 12で作り直されたDe-bleedです。RX 12のDe-bleedは、機械学習で被りを見分け、参照トラック無しでヘッドホンの漏れを消せるとうたわれています。時間合わせが要らないので、後で書く「揺らぎ」に左右されないはずです。

10日間のトライアルで試しましたが、重なった区間の漏れは減りませんでした。

推測ですが、相手のトラックに入った私の声は、ネットを経由した後の「普通の人の声」です。モデルから見ると、どちらがこのトラックの主役なのかを決められないのだと思います。用途としては今回の状況にど真ん中の機能なので、効かなかったのは意外でした。

解決:参照を「揺らぎ込み」で時間合わせする

De-bleed(Classic・参照あり)とは何か

RXのDe-bleedには、従来からの**参照ありの方式(Classic)**もあります。漏れの元(参照トラック)を渡すと、「参照 → 漏れ」の伝わり方を周波数ごとに学習し、各瞬間に漏れが占める割合だけ音を抑えます。

NLMSとの違いはここです。

  • NLMSは、波形を予測して引き算します。位相までぴったり合わないと残ります
  • De-bleedは、周波数ごとの大きさの比率で抑えます(ウィーナーフィルタ系の考え方)。多少の非線形な崩れには強い反面、参照と漏れの時間がずれると学習が崩れます

NLMS(時間領域で波形を引く)とDe-bleed(周波数ごとに大きさの比率で抑える)の対比

収録当時に試して効かなかったのは、このClassicです。マニュアルどおりファイルの頭は合わせていたので、当時は自分の使い方か方式の限界のどちらかだと思っていました。原因は別の所にありました。

時間のずれは「一定のまま小さく跳ぶ」が483回

自分のトラックと、相手トラックに入った漏れの時間関係を、全長にわたって測りました。私が話している区間で、3秒の窓を1秒ずつずらしながら相互相関(GCC-PHAT)のピークを取り、跳びを許す経路追跡でつないでいます。

すると、ずれ(lag)は収録の冒頭では +325 ms でしたが、終盤では −664 ms まで動いていました。しかも、じわじわ動くのではなく、一定のまま、ある瞬間に小さく跳ぶ動き方です。1 msを超える跳びが130分で483回、平均すると16秒に1回、最大で137 msでした。

lagの推移:階段状に跳びながら+325 msから−664 msへ動いていく。冒頭で測った1つのオフセット(点線)は35分あたりから外れていく

この動き方は、Riversideがネットワーク越しの音声を再生するときの同期補正(バッファの調整)によるものだと推定しています。相手の端末に届いた私の声が、補正のたびに少しずつ前後し、それがそのまま漏れに乗っていた、という見立てです。逆に言えば、漏れがネット経由で届いた私の声だったことは、この跳び方からも裏づけられます。混入の経路そのものは、特定できていません。

参照の合わせ方で、結果がまったく変わる

そこで、自分のトラックを跳びの位置で区切り、区間ごとに漏れの時刻へ載せ替えた参照を作りました(揺らぎ込みの時間合わせ)。合わせた後に残ったずれは、±2 msを超える所が30秒窓で20か所→1か所、5秒窓で116か所→5か所まで減っています。

この参照をDe-bleedに渡したら、重なった区間も含めて、漏れが消えました。

音声C: 元 → ゲートのみ → De-bleed(参照あり)(各6秒)

2人の声が重なる2〜5秒目(各パートの頭から)を聴き比べてください。ゲートのみ(7秒あたりから)では私の声が残り、De-bleed(13秒あたりから)では相手の声だけになります。

参照の合わせ方だけを変えて、同じ設定のDe-bleedをかけた比較もしました。

音声F: 元 → 合わせなし → 1つのオフセット → 揺らぎ込み(各6秒)

揺らぎ込みで合わせた最後のパート(20秒あたりから)だけで、漏れが消えます。

参照の合わせ方 漏れの残り(相手が黙っている区間・全長) 元からの減り
元(処理なし) −48.7 dBFS —
合わせなし −49.3 dBFS −0.6 dB
1つのオフセット(冒頭60秒で測った値を全長に) −49.6 dBFS −0.9 dB
揺らぎ込み −62.4 dBFS −13.6 dB

「1つのオフセット」は、収録当時に私がマニュアルどおりにやった「頭を合わせる」のと同じ考え方です。冒頭で測ったずれは、収録の35分あたりまでは±5 ms以内に収まっています。ところがそこから外れていき、90分台では約700 msもずれます。頭をぴったり合わせても、途中から参照が漏れとずれていく。当時、正しい手順を踏んだのに効かなかった理由は、これでした。

De-bleed相当をPythonで自前実装したら、RXと同等になった

RXのDe-bleedは、pedalboard(後述)からは参照トラックを渡せません。そのため最初は、RX 11のアプリで手作業のRenderをしていました。次の収録からは全自動で回したいので、同じ考え方の処理をPythonで書きました。

  • 参照(揺らぎ込みで合わせた自分のトラック)と相手トラックをSTFTにかける
  • 私だけが話している区間で、周波数ごとに「参照 → 漏れ」の伝わり方(大きさの比)を学習する
  • 各瞬間・各周波数について、漏れの推定量から抑える量を決める(ウィーナーフィルタ系)。抑える強さを上げすぎると相手の声も削れるので、暗騒音のレベルで止める

音声D: RXのDe-bleed → 自前実装(各9秒、どちらもゲート込み)

前半がRX、後半(10秒あたりから)が自前実装です。私の耳では聴き分けられませんでした。

数値でも、私だけが話している区間の残りは、RXが −61.2 dBFS、自前実装が −62.4 dBFS でほぼ同じです。処理時間は130分の全長で49秒でした。これで、時間合わせからDe-bleedまでを、人の手を介さずに回せるようになりました。

RXのプラグインをpedalboardでヘッドレスに回す

De-bleed以外の整音には、RXのプラグイン(VST3)をそのまま使っています。Spotifyが公開しているPythonライブラリ「pedalboard」を使うと、DAWを開かずにVST3を読み込んで、ファイルに処理をかけられます。

整音のチェーンは次のとおりです。相手トラックにだけ、最初に電源由来のハム(50 Hz)を消すDe-humを入れています。

整音のチェーン:De-bleed(自前)→ゲート→De-hum(相手のみ)→Mouth De-click→Dialogue Isolate→Mouth De-click→−16 LUFS

ここで2つ、つまずきました。

Dialogue Isolateの「Best」は、パラメータ一覧に無かった

Dialogue Isolateには品質の設定(Best)があります。ところが、pedalboardから見えるパラメータの「Quality Mode」を切り替えても、出力がビット単位で一致して変わりませんでした。

プラグインの内部状態(pedalboardの raw_state)を読み解いていくと、品質の切り替えは一覧に出てこない AlgorithmType という項目が担っていて、Bestは AlgorithmType=1 でした。内部状態を書き換えて読み込ませると、Bestで処理できます。

注意点として、raw_state を書き戻すと、それまでに設定した他のパラメータが既定値に戻ります。内部状態の書き戻しを先に済ませてから、パラメータを設定する順にする必要があります。

RX 11では、女性の笑い声が遠くなった

RX 11のDialogue Isolate(Best)では、相手の笑い声が「声ではない雑音」と判定されたのか、2か所で引っ込んでしまいました。RX 12のトライアル版で同じ処理をかけると、笑い声は元のまま残ります。

音声E: RX 11(Best)→ RX 12(各7秒)

前半のRX 11では、2秒あたりと5秒あたりの笑い声が遠くなります。後半のRX 12(8秒あたりから)では残っています。

De-bleedのために入れたRX 12のトライアルでしたが、収穫はむしろDialogue Isolateの方でした。

まとめ

  • 遠隔収録で相手トラックに混入した自分の声(経路は不明)は、NLMS・DialogueSidon・参照なしのDe-bleedでは消えなかった。AIの進歩に期待したリトライだったが、新しい手法はどれも決め手にならなかった
  • 効いたのは、収録当時に見切った参照あり(Classic)のDe-bleedに、揺らぎ込みで時間合わせした参照を渡すこと。ずれは一定のまま小さく跳ぶ形で、130分で483回あった
  • 冒頭で測った1つのオフセットでは、30分ほどしか合わない。短く試して「効かない」と判断する前に、全長でずれを測るべきだった
  • De-bleed相当はPythonで自前実装でき、RXと同等になった。次の収録からは全自動で回せる

数年越しに、ようやくあの収録を、本来聴いてほしかった音で出し直せます。この整音した音声を、話者別の字幕・無音カット・章付きのYouTube動画にするまでは、後編「整音した2時間の対談を、話者別字幕・無音カット・章付きのYouTube動画にする」に書きました。