こんにちは、皆さん。
人は雑踏の中でも、突然鳴った音へすぐ注意を向けられます。ゲーム内のagentにも同じ入口を持たせるなら、個々の音源情報を直接読むのではなく、耳に届いた音だけから「何か鳴った」「左から聞こえた」と判断できると面白そうです。
さて、今日はUnreal Engine 5.8の連続環境音に40 msの突発音を混ぜ、HRTF処理後のstereo PCMだけから検知、左右推定、候補音の抽出を行います。短い固定listener demoでは35 eventをすべて検知し、左右は34/35、**97.14%**正解しました。検知latencyは20〜60 ms、中央値20 msでした。
ただし、これは移動可能なlistenerを追加する前の短いdemo結果です。5分間のnegative試験と抽出品質のSI-SDR測定はまだ完了していません。
Unreal Engineのbinaural audio
Unreal Engine 5は2022年4月に正式公開されたreal-time 3D engineです。現在のAudio Mixerは、音源の再生だけでなく、procedural synthesis、submix、DSP、C++ APIを備えています。今回は2026年のUnreal Engine 5.8を使いました。
Resonance AudioはGoogleが2017年11月に公開したspatial audio SDKです。音源の方向を、左右の耳へ届く時間差、音量差、周波数特性の差として表すHRTF(Head-Related Transfer Function)処理を行えます。
今回の設定はBINAURAL_HIGHです。Unreal Engine 5.8のResonance Audio設定では、third-order Ambisonicsを使うhigh quality modeとして説明されています。結果はheadphone向けの左右2 channel音声になります。
ライセンス
| 対象 | ライセンス |
|---|---|
| Unreal Engine 5.8 | Unreal Engine EULA |
| Resonance Audioの公開SDK | Apache License 2.0 |
Unreal EngineはOSSではなく、利用と配布にはEULAの条件が適用されます。Resonance Audioの公開sourceはApache License 2.0ですが、Unreal Engineに同梱されたpluginやEngine codeを扱う場合は、Epicの配布条件も確認してください。
今回検証する内容
環境音とtargetを混ぜ、listenerへ届いた後のMain Output Submixを48 kHz stereoで取得します。analyzerへ渡すのはこのPCMだけです。音源actorの位置や発音状態を検知器へ直接入力しません。
- 帯域別energy riseと全帯域RMS riseで、環境音中の40 ms burstを低遅延に検知できるか
- 変化した時間周波数binのGCC-PHATとILDから、音が左か右かを推定できるか
- 検出時刻と推定方向から作ったsoft maskで、候補clipを再合成できるか
完全なC++ code、設定、Automation Test、集計JSONは、kiarina/labsのunreal-mixed-audio-attention labで公開しています。
検証環境の再現
macOSへUnreal Engine 5.8、対応するXcode、miseを用意し、UE_ROOTへEngineのroot directoryを指定します。projectはC++でbuildするため、Editorで開いている場合は先に閉じてください。
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/kiarina/labs.git
cd labs
git sparse-checkout set .gitignore .mise/tasks Makefile mise.toml \
2026/07/25/unreal-mixed-audio-attention
export UE_ROOT=/path/to/UE_5.8
mise -C 2026/07/25/unreal-mixed-audio-attention run
mise -C 2026/07/25/unreal-mixed-audio-attention run test
mise -C 2026/07/25/unreal-mixed-audio-attention run editor
最初のmise ... runはEditor targetのbuildとproject構造の検証を行います。testはsignal生成、左右推定、SI-SDR関数のAutomation Testです。editorで通常の5秒calibration demoを開きます。
PIEを開始したら、W/Sで前後、A/Dで左右へ移動し、mouseでcameraと頭部listenerを回転できます。水色のENV球または橙色のBURST球へ接触すると、その音源のon/offが切り替わります。操作をEditorへ戻すkeyはShift+F1です。
音が判断へ変わるまで
処理の流れは次のとおりです。
2つの連続環境音 + 4方向の40 ms target
-> Resonance Audio / HRTF
-> 48 kHz stereo Main Output Submix
-> 5秒ring buffer
-> 1024-point STFT、480 sample hop
├─ onset: 8帯域energy rise + 全帯域RMS rise
├─ side: 200–1500 Hz masked GCC-PHAT + 1.5–8 kHz ILD
└─ extract: 時刻と方向からsoft maskを生成
-> raw / extracted stereo WAV + run JSON + HUD
STFTは短い時間ごとに音を周波数へ分解する処理です。1024 sampleは48 kHzで約21.3 ms、hopの480 sampleは10 msに相当します。
1. 突発音を検知する
左右を合成したspectrogramを8帯域に分け、直前からenergyがどれだけ増えたかを見ます。全帯域RMSの増加も別経路で計算し、大きい方をonset scoreにします。
初版では3帯域のriseを平均しましたが、狭い帯域のburstが他の帯域に薄められました。最大帯域riseを採る経路へ変更すると、短い音へ反応しやすくなりました。thresholdはcalibration中のscore中央値とMADから決めます。
2. 左右を推定する
GCC-PHATは左右の波形の時間差を相互相関から探す方法です。今回は200〜1500 Hzのうち、onsetで変化したbinだけを使います。
高い周波数では頭による遮りが左右のlevel差として出やすいため、1.5〜8 kHzのILD(Interaural Level Difference)も投票へ加えます。GCC-PHATとILDが十分に一致しない場合は、無理に左右を決めずUnknownにします。
3. 候補clipを抽出する
eventの250 ms前から750 ms後まで、合計1秒を切り出します。event前の区間をbaselineにして、そこからenergyが増え、推定した側と整合するbinへ大きな重みを置くsoft maskを作ります。左右へ同じmaskを適用し、iSTFTで時間波形へ戻します。
左右へ同じmaskを使うのは、抽出によって新しい左右差を作らないためです。rawとextractedはPCM16 WAVとしてSaved/MixedAudioAttention/へ保存します。
実験空間と条件
内周3 mにseed固定の連続noiseを2音源、外周5 mにtargetを4音源配置しました。距離減衰は無効です。listenerが動いても、距離による音量差ではなく、頭部に対する相対方向の変化を観察します。
| 項目 | 条件 |
|---|---|
| Engine | Unreal Engine 5.8 |
| OS / machine | macOS 26.5.2 / Apple M4 Max |
| audio | 48 kHz、stereo、Resonance Audio BINAURAL_HIGH
|
| 環境音 | 左右2つのseed固定continuous noise |
| target | 40 ms chirp / seed固定noise burst |
| 方位 | -120 / -60 / +60 / +120° |
| 要求SNR | +12 / +6 / 0 / -6 dB |
| analyzer | 1024-point Hann STFT、480-sample hop |
通常demoは5秒calibration後、onになっているBURST音源を2秒ごとに巡回します。full modeは60秒calibration、300秒negative、2音種×4方位×4 SNR×5回の160 eventを予定した実装です。
画面中央のblock pawnがlistenerです。頭部位置へbinaural listenerを固定し、yawをcamera操作へ追従させています。HUD上段には現在の位置、向き、score、threshold、左右予測、confidence、lag、ILD、latencyを表示します。下段では左右2秒の波形、4秒のspectrogram、最新eventを同時に確認できます。
観測結果
2026年7月25日にUnreal MCPからPIEを起動し、block pawn追加前の固定listener demoを観測しました。
| 項目 | 観測値 |
|---|---|
| matched detections | 35 / 35 |
| correct side | 34 / 35(97.14%) |
| latency | 20〜60 ms、中央値20 ms |
| unmatched detections | 0 |
| analyzer queue overrun | 0 |
35回のtargetすべてに対応する検知があり、余分な検知はありませんでした。正解した左右は34回です。10 ms hopで処理する構成に対し、中央値20 ms、最大60 msで反応しました。
HUDでは、左右のstream波形、scrollするspectrogram、選択bin、検出線、最新eventのraw/extracted波形が同時に更新されました。WAVとrun JSONの生成も確認しました。
結果を簡単に読む
-
混ざった音だけから、短い異変と左右をかなり早く捉えられました。
Engine内部の「どのactorが鳴ったか」を答えとして使わず、HRTF後の左右音声だけを解析しています。将来、recordingやvoice chatなど、音源metadataが得られない入力へ寄せやすい構成です。
-
35/35は有望ですが、false alarmの評価ではありません。
今回の
unmatched detections = 0は短いdemo中の値です。予定している5分間のnegative試験を実行していないため、「5分に1回以下」という暫定条件を満たしたとはいえません。 -
WAVが作れたことと、音をきれいに分離できたことは別です。
soft-maskでextracted WAVは生成できましたが、post-HRTFのtarget-only referenceがありません。SI-SDRを測っていないため、抽出品質の改善は未確認です。
試行中にわかったこと
Unreal Engine上のaudio処理では、signal algorithm以外にもいくつか詰まりやすい点がありました。
- Resonance Audioはvolume補正だけでは、非focusのPIEでexternal-sendが無音になりました。PIE中だけVR focusとpause-on-focus-lossも補正し、
EndPlayで元へ戻しました。 - procedural環境音をtimerで少しずつqueueした初版は、約2秒で無音になりました。既知の実験時間分を開始時にqueueする方式へ変えました。
- 通常の
TArray<float>と非aligned設定では、UE 5.8のFFT factoryがnullを返しました。Audio::FAlignedFloatBufferと128-bit aligned設定へ変更しました。 - audio callback内は、事前確保した5秒ring bufferへのcopyだけにしました。STFTなどの解析は専用workerで処理し、callbackを塞がない構成です。
制限
- 集計値は固定listenerの短いdemoで、現在の移動可能block pawnを評価した結果ではない
- 60秒calibrationと300秒negativeを含むfull modeは未実施
- SNR別、音種別、方位別の内訳をまだ集計していない
- post-HRTFのtarget-only referenceがなく、raw/extractedのSI-SDRは未測定
- soft-mask WAVの生成だけでは、抽出品質を判断できない
- 音種の識別、前後・上下の定位、遮蔽、残響を扱っていない
- 一般化HRTFだけを使い、実耳録音や個人化HRTFで確認していない
- macOS 26.5.2、Apple M4 Max、Unreal Engine 5.8の1環境だけで観測した
- Main Outputへ、このproject以外の音が入らない前提である
検証後の感想
音源actorを参照すれば左右は簡単にわかります。それを使わず、実際にlistenerへ届いた混合音だけから35回すべてを拾い、34回の左右を当てられたのは良い感触でした。特に、画面を歩いて頭の向きを変えると、同じ音源でも左右音声と判断が変わるのをHUDで追えるため、algorithmの数字が空間内の感覚へつながります。
一方で、抽出WAVが目で確認できると成功したように感じますが、品質指標はまだ空欄です。そこを成功扱いしないことも、この検証では大切でした。低遅延の「音の注意」をNPCやinteractive agentへ渡すsensorとして、使い方を考えるのが楽しい仕組みです。
