プログラミング
商用空間オーディオエフェクトをリバースエンジニアリングした方法
How I reverse engineered a commercial spatial audio effect (epestr.com)
要約
著者は、Linux上で以前使用していた商用空間オーディオエフェクトを再現するため、リバースエンジニアリングを試みました。HRTFなどの初期アプローチの限界に直面した後、エフェクトの振る舞いを測定するためにインパルス応答やサインスイープなどのテスト信号を使用する手法に切り替えました。この測定結果からエフェクトの特性を抽出し、元の効果を近似的に再現することに成功しました。
全文翻訳
以前使用していた別のOSで、リアルタイムで再生されるオーディオを処理し、音質を大幅に向上させるプログラムがありました。そのプログラムは、音をより遠くに感じさせ、聴き取りやすくしながら、重要な情報を保持していました。私は常に、Linuxでも同様にシンプルなプログラムが欲しいと思っており、自分で作成することにしました。
オリジナルサラウンドサウンド
再生 0:00 / 0:10
ヘッドフォンで聴いてください。抜粋はBacao Rhythm & Steel BandのP.I.M.P. [0]からです。
処理されたバージョンは耳から遠く、耳に「乗っている」感じが少なくなり、しばらく音楽を聴いた後の「詰まった」感じが軽減されます。
最初の試み
Linux用に開発していたため、ベースとなるスタックはPipeWire [1]でした。オーディオに関する経験が全くなかったため、LLMを相談相手にしながら、標準的なオーディオフィルターや心理音響技術を試すことから始めました。
途中、HRTF(頭部伝達関数)について学びました。これは、特定の方向から来る音が、リスナーの方向と形状に応じて、どのように耳に届くかを記述するものです。最初にMIT KEMARデータベース [2]を試しましたが、無響室で録音されたため部屋の反響がなく、望ましい心理音響効果が得られず、ひどい音に聞こえました。結果は安っぽく、より内向きに聞こえました。後に、部屋の応答も含まれているSADIE IIデータベース [3]を見つけ、これは私が覚えていた音にずっと近くなりました。
しかし、このアプローチはすぐに限界を示しました。LLMは「知覚的包囲感」、「後期場のデコヒーレンス」、「スペクトル拡散」、「拡散空間エネルギー」といったことについて話し始めました。[1][1] Geminiはこの点で特に優れていました。学生向けの無料プログラムでもありました。何かが間違っていることは聴き取れましたが、それらの説明は私が理解したり、エフェクトを改善するために使用したりできるものには繋がりませんでした。
私は、明確な進むべき方向がないまま、プロジェクトを一時休止しました。
再試行
ずっと後になって、別のアイデアが浮かびました。プログラムが内部で使用している正確なパラメーター、技術、アルゴリズムを発見しようとするのではなく、フィルター自体の振る舞いを研究できるのではないかと考えたのです。
これは後から考えると明白ですが、プログラムのコピーを持っていませんでした。友人のラップトップでしか実験できず、実装を変更して、いつでも参照と比較することができませんでした。
Signalsmith [4]やオーディオエンジニアのブログを読み、測定すべきことについての直感を養い、フィルターのインパルス応答やその他の測定可能な特徴をキャプチャするためにプログラムを使い続ける必要はないことに気づきました。Linuxでオーディオファイルを生成し、友人のラップトップを一時借りて、生成したファイルをプログラムで再生し、出力を録音しました。
テストの組み立て
問題は、プログラムを差別化する関連する詳細を、特徴やフィルターを再録音することなくキャプチャするために、そのファイルに何を入れるべきかということになりました。
最初に見つけた有用なものは、インパルス応答測定でした。単一の非ゼロサンプルを含むファイルフィルターに渡すと、録音でそれ以降に現れるものはすべてフィルターによって追加されたことになります。時間の経過に伴う減衰の程度、減衰の形状、およびそれに関連する位相遅延などの特徴が出力に含まれます。右チャンネルにインパルスを再生すると、右で再生された音に対する両方の出力の応答を抽出し、左も同様にできます。
1つのインパルスに対する応答がhである場合、位置kにあるインパルスに対する応答は、kにシフトされx[k]でスケーリングされた同じhになります。それらの応答をすべて加算すると、次のようになります。
$$ y[n]=\sum_k x[k]h[n-k]=(x*h)[n]. $$
これはもちろん、フィルターが線形時不変フィルター [5]であることに依存します。
より大きなインパルスは、比例して大きくなった同じ応答を生成するはずです。
$$ F(ax)=aF(x). $$
2つの入力が一緒に再生されると、各入力が個別に生成するものの合計が生成されるはずです。
$$ F(x_1+x_2)=F(x_1)+F(x_2). $$
そして、同じ入力を後で再生すると、後で同じ応答が生成されるはずです。
ステレオであるため、完全な方程式は次のようになります。
$$ \begin{bmatrix} y_L\\ y_R \end{bmatrix} {}\!\begin{bmatrix} h_{LL} & h_{LR}\\ h_{RL} & h_{RR} \end{bmatrix} * \begin{bmatrix} x_L\\ x_R \end{bmatrix}. $$
すべてのモードで録音するために1つのファイルを送信していたため、レベル依存の処理を確認するために異なる振幅でインパルスを追加し、両方のチャンネルで同じインパルスを再生し、右チャンネルを反転させました。出力は、最初のケースで追加された個別の応答と、2番目のケースで減算されたものと一致するはずであり、上記の条件を確認します。また、次のインパルスが始まる前に応答が終了するように、それらの間に十分な無音を残しました。
3つのレベルのインパルス、左、右、両方のチャンネルで再生、および反対の符号で再生。
オーディオ例をダウンロード
これについて読んでいると、測定に関する論文ではインパルスではなくサインスイープを使用する傾向があることに気づきました [6]。[2][2] インパルスはすでにすべての周波数を含んでいましたが、サンプルが1つしかないため、各周波数のエネルギーはわずかでした。そのため、長いスイープは、録音ノイズから応答をより簡単に区別できるようになります。
短縮された対数スイープ。
オーディオ例をダウンロード
$$ Y(f)=H(f)X(f), \qquad H(f)=\frac{Y(f)}{X(f)}. $$
これはほとんどの範囲で機能しますが、X(f)がゼロに近い場合、録音ノイズをそれで割ると、プログラムとはほとんど関係のない、かなり印象的なフィルターになります。これは正則化されたバージョンで回避できます。
$$ \hat H(f)=\frac{Y(f)X^*(f)}{|X(f)|^2+\lambda}. $$
十分な入力エネルギーがあれば、これはほぼ同じ除算になり、一方、λは他の場所の結果を制限します。複素値は位相も保持しており、これは一方のチャンネルに送信された音が遅延を伴って他方のチャンネルに現れる可能性があるため必要でした。
スイープはインパルスと比較するための別の測定値を提供しましたが、再生にも時間がかかり、その間にプログラムは動作を変更する可能性がありました。ゲインが変化するかどうか、およびその変化にかかる時間を確認するために、異なる周波数と振幅の定常トーン、および静かな状態から大きい状態、そしてその逆へと変化する長いトーンを追加しました。
定常トーン、短縮され、両方のチャンネルで再生。
オーディオ例をダウンロード
2つのレベルを交互に切り替える1000 Hzのトーン。
オーディオ例をダウンロード
そして、新しい周波数が現れるかどうかを確認するためにマルチトーンを使用しました。
8周波数マルチトーン。
オーディオ例をダウンロード
そして時間不変性を確認するために繰り返しを使用しました。
マルチトーンの同一のコピー3つ。
オーディオ例をダウンロード
そして、抽出したフィルターが後で録音された出力を再現できるかどうかをテストするために、ノイズの一部を脇に置きました。
低域、中域、高域を順番にカバーするノイズ。
オーディオ例をダウンロード
フィルターの復旧
録音と再生は手動で開始されたため、録音にはまずオフセットが必要でした。開始近くに均等に間隔を空けたクリック音を入れていましたが、最初は元のクリック音と録音を相関させて見つけようとしました。フィルターは各クリックを時間とともに広げることができるため、ウィンドウW内の二乗サンプルを合計して、その周囲のエネルギーを推定しました。
8つの同期クリック。
オーディオ例をダウンロード
$$ e[n]=\sum_{m\in W}\left(y_L[n+m]^2+y_R[n+m]^2\right), $$
そして、次のオフセットを見つけました。
$$ \hat\tau=\arg\max_\tau\sum_n e_{\text{probe}}[n]e_{\text{recording}}[n+\tau]. $$
遅延を保持するために、両方のチャンネルに同じオフセットを適用しました。
出力
メインのサラウンド設定では、スケーリングされたインパルスはほぼ同一であり、結合された入力は個別の応答の合計と一致し、マルチトーンはほとんど新しい周波数を生成しませんでした。再構築された出力と録音を比較するために、次の式を使用しました。
$$ \varepsilon=\frac{\lVert y-\hat y\rVert_2}{\lVert y\rVert_2}, $$
ここで、yは録音された出力であり、ŷは抽出されたフィルターによって生成された出力です。同一の繰り返しセクションはすでにわずかに異なっていたため、モデルのエラーをその差と比較しました。
$$ \varepsilon_{\text{model}}\approx\varepsilon_{\text{repeat}}