ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Physical Audio Spatialization Engine(PHASE)によるジオメトリを意識したオーディオの実現
ジオメトリを意識したオーディオによって、Appやゲームに複雑でインタラクティブな、没入感のあるオーディオシーンを構築する方法を紹介します。Appleの空間オーディオAPIであるPHASEを紹介します。Physical Audio Spatialization Engine(PHASE)がどのようにして常にユーザの体験に沿ったサウンドを維持するか、そしてポストプロダクションを待たずに、開発プロセスの中で空間的なサウンドスケープやシーンを作るのに役立つかをご確認ください。ソース、リスナー、アコースティックジオメトリ、マテリアルなど、APIとそのクラスの概要を説明し、空間モデリングのコンセプトを紹介します。また、PHASEの基本的な構成要素を素早く組み合わせて、Appやゲームに統合されたオーディオ体験の構築を開始する方法もお伝えします。
リソース
関連ビデオ
WWDC23
WWDC22
-
ダウンロード
こんにちは 私の名前はバラースです AppleのCore Audio チームに所属しています 今日はPHASEを使った ジオメトリに意識した オーディオについて お話させていただきます 新しいフレームワークである なぜPHASEを 使いたくなるのかについて お話します 今回は フレームワークが 提供する機能を ご紹介します 概念をより深く理解 していただくために APIを使ったサンプル 使用例をご紹介します 後ほど デビッド・タールに 引き継ぎます では始めましょう オーディオはゲーム体験の 重要な要素です ヘッドホンでの空間音響 ゲームプレイ全体を さらに進化させ 現代のゲームでは より多くのことを 感じさせてくれます 物理学 アニメーション 視覚効果 エンジンのさまざまな サブシステムなど 互いにコミュニケーションを とりながら プレイヤーの行動に応じて ゲームの流れや ストーリーを進む しかし オーディオの サブシステムは一般的に 別々に 管理されています また ミドルウェアで 作成されることもあります シミュレーションを 意識しないこともあります オーディオアセットは ゲームにマッチした ストーリーを伝えるために ポスト生産 プリベイクが 手動で調整されます ビジュアルの進化に合わせて システム サウンドデザイン および関連資産の 再生する必要があります 様々な プラットフォームで 一貫性のある オーディオ体験を このような反復的な 開発プロセスは ゲーム開発中に説明する 必要があります これにより オーディオ 体験がゲームプレイの ビジュアル面に遅れることが よくあります
より良いゲームオーディオ 体験を提供するために 私たちが望むのは オーディオシステムを サブシステムと近づけたい また より簡単に できるようにしたい 提供するAppを 作りやすいものにしたい 一貫した空間的な オーディオ体験 これらすべての デバイスをサポートしたい
では 次の説明をします 新しいフレームワーク PHASEと機能の紹介です PHASEは 新しいフレームワークです ジオメトリ情報をオーディオ エンジンに加えることで サウンドデザインに配慮し 親しみやすくなり イベントドリブンな オーディオ再生システム Appを 書くことができる 一貫した空間的な オーディオ体験を また既存のオーサリング ソリューションや パイプラインとの 統合も可能です PHASEの 詳細に入る前に ゲームオーディオで使われる ワークフローを見ましょう こちらが例です アウトドアシーン リスナー 音源がある場合 小川の流れとオクルーダー 建物です オクルーダーとは シーン内のオブジェクト 音源とリスナーの 間の音を 減衰させることができます 次のように配置します 複数の点光源 エリアに沿って配置します リスナーの動きに合わせ 様々なテクニックを 駆使して レイトレーシングなど 適切なフィルタリングと 混合比率を決定する ポイントソースの間を 手動でブレンドすることで 良好な オーディオを 楽しむことができます ゲーム開発の 自然な流れの中で ビジュアルシーンが変わる 例えば 例のシーンの 建物の オーディオ体験を アップデートして 視覚的なシーンの 変化に合わせます Appが 作れることを想像して オーディオソースが 点ではなく シーンに応じて 管理し 混合する必要があります ある範囲から 発せられる音として あるいはオーディオ システムの音量 自動的に 管理できます PHASEはまさに それを実現します ボリューメトリックの導入 新しいフレームワークでは 音源を幾何学的な形状とし オーディオエンジンへ APIを提供します その上 音量のある音源の場合 オクルーダーを通過できます 幾何学的な形として シーンに登場します また音響素材のプロパティを プリセットから選択して オクルーダーに 装着することができます PHASEフレームワークでは 媒体伝搬を設定できます 点光源の指向性を 設定できます Appの要求に応じて アウトドアシーンを見てます ここでは例として ただし インドアシーンが ある場合は 早期反響音を選択でき レイトリバーブの プロパティをプリセットの ライブラリから選択できます フレームワークに伝えると 様々な音源の場所を オクルーダー リスナーは PHASEはシーン内の 様々な音源の効果など 手間のかかる部分を サポートして モデル化してくれます これでオーディオシステムは ジオメトリを認識しています 視覚的なシーン合わせて 変更することができます より早く ゲーム開発の 進化に合わせて
ジオメトリに加えて を意識しています PHASEは 双方向 再生システムの イベントベースも提供します サウンドイベントは PHASEの再生イベントを 記述するための基本単位です 選択範囲をカプセル化し ブレンドします 再生をカプセル化します サウンドイベントには シンプルなイベントから ワンショットやループ再生の 複雑なシーケンスを ツリーのように構成し 再生イベントのサブツリーを ブレンドしたり 切り替えたり することができます 足音を再生するという 簡単な例を見てみましょう ここではランダムな ノードを用意します 自動的に 3種類の中から選択します 砂利を踏む足音 もうひとつの音のイベントは 布のざわめきです どちらのイベントツリーも 他のツリーに接ぎ木できます この例では「near」です 布の音と足音を ミックスして再生します もうひとつの木ができます この例では「far」です 別の音を 奏でるために キャラクターが 遠くにいるときに 近くの木と遠くの木が 混在する ゲーム性に応じて距離を コントロールできました 雪や草の上での足音の ようなイベントツリーを 追加することができます 例えば 再生イベントの複雑な シーケンスを構築して インタラクションや 物理やアニメーションの サブシステムで引き起す
PHASEでは 音の再生は 鳴らすことができます チャンネル構成でも 3D空間でも あるいは周囲の ベッドとして 音には向きはあっても 位置はない 基本となるエンジンは が構築されています 空間オーディオ レンダリング機能 iOS macOSデバイスで 利用可能です Air Podsシリーズの ヘッドフォンにも対応 Appを 構築することができます 一貫した空間オーディオ 体験を提供する 対応するデバイスで 自動的に生成します 次は デビッド・タールから 案内します PHASEをより深く理解する ためのお話しです コンセプトやAPIについて 詳しく説明します こんにちは 私はデビッド・タールです PHASEのシステムアーキ テクト兼 開発リーダーです 今日は PHASEのAPIを ご紹介します このセクションでは 一般的な概念を紹介します これに続いて いくつかのサンプル 使用例を紹介します PHASE APIは 3つの主要な コンセプトに分けられます アセットを管理するエンジン ノードは再生をコントロール ミキサーは 空間化を制御します PHASEエンジンは 次のように分解できます 3つの主要セクション アセットレジストリ シーングラフ レンダリング アセットをエンジンの ライフサイクルの中で 登録や解除をします PHASEは サウンドアセットと サウンドイベントアセットの 登録をサポートしています サウンドアセットは直接 ファイルから読み込めます あるいは 生のオーディオ データとして アセットに ロードし エンジンに直接 読み込むことができます サウンドイベントアセットは サウンドの再生を制御する 1つ以上の階層的なノードと 空間化を制御する ダウンストリームミキサーの 集合体です シーングラフとは シミュレーションに関係する オブジェクトの階層です これにはリスナーやソース オクルーダーが含まれます リスナーとは 空間上の 位置を表すオブジェクトです シミュレーションを 聞くことができます ソースはオブジェクトです 音が発生する場所を 表すものです 先程 バラースが述べたように PHASEはポイントソースと ボリューメトリックの 両方をサポートしています オクルーダーとはジオメトリ 表すオブジェクトです シミュレーションの中で 音の伝達に影響を与え 環境の中を 移動します またオクルーダーには 音の吸収・伝達に影響する 素材が 割り当てられています PHASEには オクルーダーに割り当てる ことができる素材 プリセットのライブラリが 付属しており ダンボール箱 ガラス窓 レンガの壁などを シミュレートできます シーンにオブジェクトを 追加すると それらを 階層化し エンジンのルート オブジェクトに取り付けます 直接的にも間接的にも 確実にシミュレーションに 参加してもらう フレームからフレームへ レンダリングステートは サウンドイベントの再生と オーディオIOを管理します エンジンを作成したとき オーディオIOは無効になる これによりアセットを 登録することができ シーングラフを作成したり サウンドイベント構築が可能 オーディオIOを 実行することなく 他のエンジン操作を 行うことができます 準備ができたらサウンド イベントを再生できます エンジン始動ができます これにより 内部的に オーディオIOを開始します 同様にサウンドイベントの 再生が終わり エンジンを停止します これでオーディオIO停止 そして再生中のサウンド イベントを停止します PHASEのノードは コンテンツ再生を制御します ノードとは階層的に 配置されたオブジェクトです オーディオ再生を 制御します ジェネレーターノードで オーディオ制作します ノード階層の中で 常にリーフノードです コントロールノードは 選択のロジックを設定します 空間化の前に パラメータ化されます コントロールノードは 常に親ノードであり 以下のように 編成することができます 複雑なサウンドデザインの シナリオを作れます サンプラーノードは ジェネレーターノードの一種 サンプラーは登録された サウンドアセットを再生 構築した後は プロパティを設定できます サンプラーノードに設定し 正しく再生ができます 再生モードとは ファイルを どう再生するかを決めます 再生モードを OneShotに設定すると 音声ファイルは一度だけ再生 され 自動的に停止します 効果音を鳴らすなど 「Fire and Forget」の シナリオで使用できます 再生モードを ループ再生に設定すると オーディオファイルは 無期限に再生されます サンプラーを停止するまで 無限に再生されます cullオプションは 音が 聞こえなくなったときの 処理をPHASEに 指示します cullオプションを 終了させるという 設定にして 音が 聞こえなくなると 自動的に停止します もし cullオプションを sleepに設定した場合 音が聴こえなくなると レンダリングを停止します 聞こえるようになると再び レンダリングを開始します これにより 手動で音を 出したり止めたりしなくても 済むようになりました エンジンによって cullされるときに キャリブレーションレベルは デシベルSPLで表示し 音のレベルを設定します また PHASEは4種類の コントロールノードをサポート ノードの種類は ランダム スイッチ ブレンド コンテナ ランダムノードは 重み付けされた無作為の 選択に従って 子供の1つを選択します 例えば この場合は サウンドイベントが発生した ときに 左のサンプラーが 右のサンプラーよりも 4:1の確率で選択されます スイッチノードは パラメータ名に基づいて 子ノードを切り替えます 例えば 地形スイッチを 「きしむ木」から 「やわらかい砂利」 に変更します 次にサウンドイベントが トリガーされたときに パラメータ名と一致する サンプラーが選択されます ブレンドノードは 子ノードの間で パラメータ値に基づく 例えば 濡れたパラメータを ブレンドノードに 割り当てることができます ブレンドするのは 大きな足音とドライエンドの 静かな水しぶきと 静かな足音とウェット エンドの大きな水しぶき コンテナノードは すべて子に一度再生します 例えば 足音を再生する サンプラーが1つと 衣服の音を再生する サンプラーもあります ゴルテックスジャケットの フリルの音のようなもの 毎回コンテナが 起動されます 2つのサンプラーが 同時に再生されます
PHASEコントロールの オーディオコンテンツを実現 PHASEはチャンネルを サポートしています アンビエント スペース ミキサーをサポートしてます チャンネルミキサーは 空間や環境演出を行わず オーディオをレンダリング チャンネルミキサーを使い ステムベースのコンテンツを 出力デバイスに直接 レンダリングする必要がある ステレオ音楽やチャンネルの 物語の対話などです アンビエントミキサーは オーディオを外部化する 距離感のモデリングや 環境効果はありません リスナーが 頭を回転させます 音は 引き続き 聞こえてきます 空間の同じ 相対的な位置 マルチチャンネルに アンビエントミキサーを使用 環境でシミュレート されていないが 宇宙のどこかから 聞こえてくるような音です 例えば 大きな森の中でコオロギの 鳴き声をバックに Spatial Mixerは 完全な空間化を行います 音源がリスナーに対して 相対的に移動すると 知覚される位置が 変化して聞こえます パンニング ディスタンス ディレクティビティの アルゴリズムに基づいて レベル 周波数特性を調整 この他にも ジオメトリを意識した 環境効果 ソースとリスナー間の パスに適用されます ヘッドフォンをしていれば バイノーラルフィルターの 適用による 外付け化も得られます 音に空間ミキサーを使う 環境シミュレーションを 行う必要があります 空間ミキサーのサポート 2つのユニークなアルゴ リズムをサポートします 距離による自然な 減衰のために標準的な 幾何学的拡散損失を 設定することができます 自分の好みに合わせて 効果を強めたり 弱めたりすることもできます 例えば 値を 下げることができます 遠くの会話をブームマイクで 録音したい場合など もう一方では スペクトルです 距離に応じた減衰の 完全なピースワイズ 曲線セグメントを 追加することができます 例えば 最初と最後は 自然な距離感で減衰させ 中間部では減衰量を 減らして 重要な会話を より遠くまで聞こえるように するというセグメントを 作ることができます 距離が伸びても 重要な台詞が 聞こえるようにします ポイントソースに対しては 2種類の指向性 モデリングアルゴリズムを サポートしています カーディオイド指向性 モデリングを空間ミックスに 加えることができます 簡単な改造で 人間の スピーカーをカーディオイド 指向性パターンでモデリング したり アコースティックな 弦楽器の音をハイパー カーディオイド指向性 パターンでモデリング したりもできます また コーンを使った 指向性モデリングも可能です このクラシックモードでは フィルタリングを制限可能 特定の回転範囲内で 制限することができます 空間ミキサーは 空間的な パイプラインに基づいて ジオメトリを意識した 環境効果を実現します 空間パイプラインは 有効または無効にする 環境効果と それぞれの 送信レベルを選択します PHASEはダイレクトパス アーリー・リフレクション レイト・リバーブに 対応しています ダイレクトパス オクルー ジョンをレンダリングします ソースと リスナーとの間の 遮蔽音は注意が必要です ある程度のエネルギーは 物質に吸収されます 他のエネルギーは 物体の反対側に 伝わります 初期の反射は 直進経路に強度の 修正と色付けを もたらします これらは 通常 鏡面反射を利用しています 壁や床の鏡面反射を利用 また 広い空間では響きが 目立つようになることを 感じられます レイト・リバーブは 環境の音を再現します 拡散したエネルギーを 高密度に蓄積して 最終的には 聞こえる 表現に集約されます 空間の 部屋の大きさや形の 手がかりとなるだけでなく 包み込まれるような 感覚を与えます さて 概念を確認したところで PHASEエンジンの背後にある コンセプトをご紹介しました ノードとミキサー コンセプトをまとめます 使用例をいくつか紹介します このセクションでは ファイル再生の説明をします 空間オーディオの構築を 体験できます 行動する音のイベント作りを 提供します この3つの重要な領域 3つの重要な領域を見れば PHASE機能を幅広く知れます 分かりやすく紹介し 深く掘り下げていきます 中盤と終盤に向けて より興味深い 能力を発揮します はじめに オーディオファイルの 再生方法をご紹介します まず PHASEエンジンの インスタンスを作成します 音声ファイルの URLを取得します 音源をPHASEに 登録します 後で参照できるように 「drums」と名付けます ここではエンジンを作ります サウンドアセットの 登録をコードで入力します まず PHASEエンジンの インスタンスを 自動更新モードで作成します これが望ましい モードです ここではこのモードを使って シンプルな再生のデモをします なお ゲームなどで正確な フレーム更新との同期が 必要な場合は マニュアル モードが良いでしょう ドキュメントを 参照してください オーディオファイルの URLを取得します。 Appバンドルに 格納されています これは モノラルの24bit 48kHzのWAVファイル 用意されたドラム ループサンプルです サウンドアセットを エンジンに登録する際に 追加の引数があります 後で参照できるように サウンドアセットには 固有の名前 サウンドアセット内の オーディオデータを あらかじめ常駐メモリに プリロードします リアルタイムでメモリに 流し込むのではなく ドラムループがかなり 短いので問題ないでしょう 何度か再生してみたいと 思うかもしれません 連続再生ができます また 出力デバイスの ラウドネスを調整するために サウンドアセットをノーマ ライズすることも選択します 一般的には 入力を正規化 することをお勧めします これによりコンテンツを サンプラーに割り当て 目標とする出力レベルを 設定した後にコンテンツを 簡単にミックスできます エンジンにサウンド アセットを登録しました サウンドイベント アセットを作成します チャンネルレイアウトから ミキサーを作成します サンプラーノードの作成 サンプラーノードは サウンドアセットの登録名と ダウンストリーム チャンネルミキサーへの 参照を受け取ります サンプラーノードの基本的な プロパティを設定します 正しく再生 できるようにします 再生モードがあるか どうかを設定します サンプラーがファイルを ループ設定できるかどうか確認 キャリブレーション レベルが設定されます ミックス内でのサンプラーの 音量を設定します サンプラーノードの 出力を接続したので チャンネルミキサーの 入力に接続します 基本的なパラメータを設定 サウンドのイベント アセットをエンジンに登録 ここではサウンド イベントアセットを登録 「drumEvent」で登録します この名前を使って 後で参照します サウンドイベントアセットを コードで登録します ChannelLayoutTagから ChannelLayoutを作ります チャンネルミキサーを モノラルで初期化し レイアウトを作成します サンプラーノードを作成し 「drums」でパスします これはモノラルの ドラムアセット 以前に登録していた エンジンを使用しています サンプラーノードはチャン ネルミキサーに送られます 再生モードを ループします 確実に音が継続して 再生されるようになります 明示的に停止するまで コードを表示します CalibrationModeを 相対的Splに設定します レベルを0に設定します これで 快適な リスニングレベルを 体験することができます サウンドイベントアセットを エンジンに登録します 後で参照できるように 「drumEvent」でパスします 再生用のサウンドイベント 作成時の参照のため サウンドイベントアセットが 登録されると インスタンスを作成し 再生を開始できます まずは サウンド イベントを作成します drumEventのサウンド イベントアセットを登録 これでサウンド イベントができたので 次は エンジンをかけてみましょう オーディオIOを起動します 音声を聞くことができます 出力デバイスへの供給 最後にサウンド イベントを開始します この時点で読み込まれた サウンドアセットは サンプラーで 再生されます チャンネルミキサーへ 出力にリマップされる フォーマット 出力デバイスで 再生されます ここでは「soundEvent」を コードで起動します サウンドイベントのアセット という名前で構成されます 登録されたサウンドアセット これですね 「drumEvent」 をパスしています 先に進みます エンジンを起動します オーディオIOを起動し サウンドイベントを開始
サウンドイベントの 再生が終了すると エンジンをきれいにできる まず 音の イベントを止めます そして エンジンを止める オーディオIOを停止し サウンドイベントを停止 サウンド「drumEvent」の 登録を解除します サウンド「drum」の 登録を解除します 最後にエンジンを破棄します ここではコードで きれいにします サウンドイベントを停止する ドラムループを 聴き終わったら エンジンを止めます それは内部で停止します オーディオIOを停止します 次に「drumEvent」で 登録解除します 「drum」でサウンド アセットを登録解除します 最後にエンジンを破棄します
基本的なことを 説明しました ここからは シンプルな PHASEの空間的な オーディオ体験です 空間ミキサーなどを 紹介します 音量のある音源 そしてオクルーダー まずは サウンドイベント アセットの登録です エンジンに登録します この例では すでにドラムの音がある エンジンから始めます イベントを登録しました ここからはミックスを アップグレードします チャンネルベースの再生から 完全な空間化を実現します まずは 空間パイプラインの構築 異なる環境効果を 選択的に適用するために 音源に 次に 空間パイプラインから 空間ミキサーを作成します 構築された後は 基本的なプロパティを設定 空間ミキサーに設定して 正しく再生されるようにします この例では 距離モデルを設定します 距離によるレベル減衰を コントロールします 指向性モデルを 設定する リスナーに対する 音源の角度に応じて レベルの減衰を 制御することができます サンプラーノードを 作成します サンプラーノードは 登録された名前と 空間ミキサーへの 参照を受け取ります 次はサンプラーノードの 基本的な設定をします 正しく再生 できるようにします 再生モードに 加えて キャリブレーションレベル Cullオプションを設定します PHASEに伝達するものです サンプラーの音が 聞こえなくなったら サンプラーノードの 出力を 空間ミキサーの入力に 接続しました パラメータを設定します イベントアセットを エンジンに登録します ここでは 前と同じ名前を使います ここでは「asset」を コードで作成します 「spatialPipeline」を 作ります 「.directPathTransmission」 をレンダリングします 「.lateReverb」 を設定します 直接音と反響音の比率を コントロールするために 「.lateReverb」の 「.sendLevel」を設定します 以下を選択します 「.mediumRoom」 レイト・リバーブの シミュレーションを行います 次に 空間的な ミキサーを作成します 次に 自然な音を出すために GeometricSpreadingDistance それを 空間ミキサーに 割り当てます 「cullDistance」を 10mに設定します もしソースがこの距離を 超えた場合 ミックスから自動的に カットしたいと思います そして「rolloffFactor」を 少し調整します 距離減衰効果を強調 しないようにします 次にサンプラーノードを 作成します 「drums」でパスします エンジンに登録した ドラム音源を参照します エンジンに登録しました 再生モードを 「.looping」に設定します キャリブレーションを 「.relativeSpl.」に設定 サンプラーの レベルを+12にして 出力レベルを上げます 「cullOption」を 「sleep」に設定します 最後に 「soundEventAsset」を登録 「drumEvent」でパスして 次のことができます 後で 参照できるようにします これで サウンドイベント アセットが登録されました 次はシミュレーション用の シーンを作ります これには リスナー ソースを作成します オクルーダーを作成します この例では オクルーダーを配置します ソースとリスナーの間に 配置します まずリスナーを作ります トランスフォームを設定 リスナーを アクティブにします シーングラフの中で ルートオブジェクト または子オブジェクトの 一つにアタッチします ここでは コードで リスナーを設定します まず リスナーを作成します トランスフォームを設定 この例では リスナーを回転させずに 原点に設定します エンジンのルートオブ ジェクトにアタッチします
ボリューメトリックソースを 設定してみましょう メッシュからソースの 形状を作成します シェイプから ソースを作成します ボリューメトリックソース を構築します トランスフォームを設定 ソースを アクティブにします シーングラフの中で それをエンジンの ルートオブジェクト または子オブジェクト ボリューメトリックな ソースを設定します まず メッシュを作成し 「HomePod Mini」の 大きさにします 続いて メッシュから 形状を作成します 複数のインスタンスを 構築するために 再利用することができます 例えば 複数の 「HomePod Minis」を シミュレーションの中に 同じメッシュを共有します
ボリューメトリック ソースを作成します なお 形状を入力としない バージョンの イニシャライザを使用し 単純な点光源を 作成することもできます トランスフォームを設定 ソースをリスナーの 2m前に 平行移動させ リスナーに向かって 回転させると 向き合うようにします エンジンのルート オブジェクトに添付します
では オクルーダーを 設定してみましょう まず メッシュから シェイプを作成します
続いて ダンボール素材 シェイプに割り当てます ジオメトリと関連する マテリアルがあります シェイプから オクルーダーを作ります トランスフォームを設定 オクルーダーを アクティブにします シーングラフの中で エンジンのルート オブジェクトまたは 子オブジェクトに取付け オクルーダーを コードで設定します 「boxMesh」を作成します それに合わせて 寸法を調整します メッシュから 形状を作成します 構築するために 再利用ができます 複数のインスタンスの オクルーダーを作れます 例えば シミュレーションの 中に複数の箱を 同じメッシュを配置できます ダンボール箱のプリセット からマテリアル そのマテリアルを を形状に割り当てます
シェイプから オクルーダーを作ります トランスフォームを設定 オクルーダーを変換します 1メートル前に リスナーの方向に 回転させ 向き合うように します これによりオクルーダーは ソースとリスナーの中間に 位置することになります エンジンのルート オブジェクトに取付けます
この時点でソースとリスナー の間にオクルーダーがいる シーンがあります
登録したサウンドイベントを イベントを作成します サウンドイベントアセット を作成し それを関連付けます リスナーをシーングラフに 関連付けます イベントを開始すると ドラムループが聞こえます 小さなボリューメトリック から再生されるソース ダンボールの 反対側には 音のイベントを開始する をコードで表現します ソースとリスナーを 関連付けます イベントに組み込み 空間ミキサーに関連付けます サウンドアセットから 「soundEvent」を作成 「drumEvent」という名前 残りの部分は 前と同じです エンジンの動作を確認し サウンドイベントを開始 空間オーディオを 取り上げました 複雑なサウンドイベントの 構築方法をご紹介します サウンドイベントの構成を することができ 振る舞いの階層化 サウンドデザインのため このセクションでは 連続した例を通して 各タイプの サウンドイベントノード サウンドイベントを作成 ノイズの多いゴアテックスの 服を着たアクター さまざまな 地形を歩く 路面の濡れ具合が 変化する 足音を再生する サンプラーノード 軋む木の上を歩く 足音を再生します コードでサンプラー ノードを作ります 登録されたサウンドアセット この例では 「footstep_wood_clip_1 」 このノードと他のノード が再生されます 1台で構成された チャンネルミキサー 今度はランダム性を加えます ランダムノードを作ります 2つの子サンプラーノード 微妙に違う きしむ木の上の 足音のサンプルを再生します コードではサンプラー ノードを2つ作ります 1つ目は登録された サウンドアセットを使用 「footstep_wood_clip_1」 そして2つ目は 登録済のサウンドアセット 「footstep_wood_clip_2 」と 名付けられています 次にランダムな ノードを作成します サンプラーノードを 子として追加します なお 各子ノードには 重み付け係数が適用されます 子ノードの可能性を 制御するために 選ばれる可能性を コントロールします この場合 最初の子は 2の子よりも選ばれる 確率が2倍になります
次は 地形の スイッチを追加します スイッチノードを作り 2つの子として作成します ここでは 2番目は ランダムに再生します 砂利の上の足音は 木の 上のランダムな足音ではない 地形のパラメータを使って スイッチを制御します。 コードでは サンプラー ノードを2つ作ります 1つ目は 登録された サウンドアセットを使用 「footstep_gravel_clip_1 」 そして 2つ目は 登録済サウンドアセット 「footstep_gravel_clip_2」 という名前です 次に作成するのは ランダムなノード サンプラーノードを 子として追加します 次は 地形の パラメータを作成します デフォルトの値は 「creaky_wood」になります スイッチノードを作成します 地形のパラメータで 制御します 2つの子を追加します 「wood random node」と 「gravel」ランダムノード このように パラメータを 「creaky_wood」にすると ランダムに木が 選択されます 同様に パラメータを 「soft_gravel」にすると 砂利のランダムノードが 選択されます
次に 濡れた ブレンドを追加します 地形スイッチノードと ブレンドノードを作成します ランダムスプラッシュノード を子として作成します 新しいランダムノード ランダムな水しぶきの 音が聞こえます 地形スイッチが入ると 決定します アクターの足が きしむ木の 上を歩いているかどうか 柔らかい砂利の上かを判断 乾いた足音との乾いた 足音とのブレンド 水しぶきの大きさは 湿り気のパラメータに依存 完全に乾いた状態から 大きな音で足音も水しぶきも 完全に濡れた状態から 静かな足音がしない 大きな水しぶきがある コードでサンプラー ノードを2つ作ります 1つ目のノードは登録済みの サウンドアセットを使います 「splash_clip_1」そして2つ目は 登録済サウンドアセットを使用 「splash_clip_2 」という名前 次に ランダムノード を作成し 子として追加します 次に作成するのは 濡れたパラメータを作成 範囲は0から1になります デフォルト値は 0.5となります 私が設定できる パラメータ ゲームでサポートされる任意 の値と範囲に設定できます ブレンドノードを作成します ウェットネスの パラメータで制御します 2つの子を追加します 地形スイッチノード ランダムノードを追加 パラメータを0にすると きしんだ木や砂利の上では 乾いた足音しか聞こえません 地形にもよりますが 濡れ具合を 0から1にしていきます 音量を大きくしていきます 水しぶきの音 足音に合わせて 水しぶきの 音を大きく 濡れた地形を再現
最後に コンテナ ノードを作成します ブレンドとランダムな clothingを子として作成 新しい「noisy clothing」 ノードは アクターが 足を踏み出すたびに ゴアテックスジャケットの 音を再生します 地形の変更について 可変湿潤度 このような最終的な ノード階層を持つ シーンを歩いている俳優の 完全な表現があります 俳優が一歩 歩くたびに ジャケットのフリルの 音が聞こえてくる きしんだ木や柔らかい 砂利の上を歩く足音 地形のパラメータに応じて 使用しています これに加えて 多かれ少なかれ耳にする 足を踏み出すたびに 足音が聞こえてきます 濡れ具合に応じての パラメータを設定します コードでは 2つのサンプラー ノードを作成します 1つ目のノードは 登録済のサウンドアセット 「gortex_clip_1」2つ目は 登録済サウンドアセット 「gortex_clip_2 」です ランダムなノードを作成し サンプラーノードを追加 子として追加します 最後に「actor_container」 ノードを作成します 2つの子を追加します 「wetness_blen」ノードと 「noisy_clothing_random」 ノードを追加します これらのノードを 合わせると完全な音 俳優の 復習では どう再生するか を学びました 続いて 知識を 深めるために 真っ先に構築したのは シンプルで効果的な 空間的なオーディオ体験 リスナーについて学びました ボリューメトリックソース そしてオクルーダーです 最後に 動作サウンド イベントの構築を学びました サウンドデザインのため ここでは ランダムに 接ぎ木について学びました スイッチ ブレンド コンテナノードを 階層化された 双方向 サウンドイベントの形成 一緒に これで 大まかに PHASEの仕組みを 理解しているはずです 基盤となるシステムに 深く入り込めます コンポーネントに深く 入り込むことができます ジオメトリを意識した ゲームオーディオ体験 ありがとうございました WWDC21をお楽しみください [アップビート音楽]
-
-
18:31 - Create an Engine and Register a Sound Asset
// Create an Engine in Automatic Update Mode. let engine = PHASEEngine(updateMode: .automatic) // Retrieve the URL to an Audio File stored in our Application Bundle. let audioFileUrl = Bundle.main.url(forResource: "DrumLoop_24_48_Mono", withExtension: "wav")! // Register the Audio File at the URL. // Name it "drums", load it into resident memory and apply dynamic normalization to prepare it for playback. let soundAsset = try engine.assetRegistry.registerSoundAsset(url: audioFileUrl, identifier: "drums", assetType: .resident, channelLayout: nil, normalizationMode: .dynamic)
-
20:47 - Register a Sound Event Asset
// Create a Channel Layout from a Mono Layout Tag. let channelLayout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_Mono)! // Create a Channel Mixer from the Channel Layout. let channelMixerDefinition = PHASEChannelMixerDefinition(channelLayout: channelLayout) // Create a Sampler Node from "drums" and hook it into the downstream Channel Mixer. let samplerNodeDefinition = PHASESamplerNodeDefinition(soundAssetIdentifier: "drums", mixerDefinition: channelMixerDefinition) // Set the Sampler Node's Playback Mode to Looping. samplerNodeDefinition.playbackMode = .looping; // Set the Sampler Node's Calibration Mode to Relative SPL and Level to 0 dB. samplerNodeDefinition.setCalibrationMode(.relativeSpl, level: 0) // Register a Sound Event Asset with the Engine named "drumEvent". let soundEventAsset = try engine.assetRegistry.registerSoundEventAsset(rootNode:samplerNodeDefinition, identifier: "drumEvent")
-
22:21 - Start a Sound Event
// Create a Sound Event from the Sound Event Asset "drumEvent". let soundEvent = try PHASESoundEvent(engine: engine, assetIdentifier: "drumEvent") // Start the Engine. // This will internally start the Audio IO Thread. try engine.start() // Start the Sound Event. try soundEvent.start()
-
23:05 - Cleanup
// Stop and invalidate the Sound Event. soundEvent.stopAndInvalidate() // Stop the Engine. // This will internally stop the Audio IO Thread. engine.stop() // Unregister the Sound Event Asset. engine.assetRegistry.unregisterAsset(identifier: "drumEvent", completionHandler:nil) // Unregister the Audio File. engine.assetRegistry.unregisterAsset(identifier: "drums", completionHandler:nil) // Destroy the Engine. engine = nil
-
25:14 - Create a Sound Event Asset
// Create a Spatial Pipeline. let spatialPipelineOptions: PHASESpatialPipeline.Options = [.directPathTransmission, .lateReverb] let spatialPipeline = PHASESpatialPipeline(options: spatialPipelineOptions)! spatialPipeline.entries[PHASESpatialCategory.lateReverb]!.sendLevel = 0.1; engine.defaultReverbPreset = .mediumRoom // Create a Spatial Mixer with the Spatial Pipeline. let spatialMixerDefinition = PHASESpatialMixerDefinition(spatialPipeline: spatialPipeline) // Set the Spatial Mixer's Distance Model. let distanceModelParameters = PHASEGeometricSpreadingDistanceModelParameters() distanceModelParameters.fadeOutParameters = PHASEDistanceModelFadeOutParameters(cullDistance: 10.0) distanceModelParameters.rolloffFactor = 0.25 spatialMixerDefinition.distanceModelParameters = distanceModelParameters // Create a Sampler Node from "drums" and hook it into the downstream Spatial Mixer. let samplerNodeDefinition = PHASESamplerNodeDefinition(soundAssetIdentifier: "drums", mixerDefinition:spatialMixerDefinition) // Set the Sampler Node's Playback Mode to Looping. samplerNodeDefinition.playbackMode = .looping // Set the Sampler Node's Calibration Mode to Relative SPL and Level to 12 dB. samplerNodeDefinition.setCalibrationMode(.relativeSpl, level: 12) // Set the Sampler Node's Cull Option to Sleep. samplerNodeDefinition.cullOption = .sleepWakeAtRealtimeOffset; // Register a Sound Event Asset with the Engine named "drumEvent". let soundEventAsset = try engine.assetRegistry.registerSoundEventAsset(rootNode: samplerNodeDefinition, identifier: "drumEvent")
-
27:05 - Set Up a Listener
// Create a Listener. let listener = PHASEListener(engine: engine) // Set the Listener's transform to the origin with no rotation. listener.transform = matrix_identity_float4x4; // Attach the Listener to the Engine's Scene Graph via its Root Object. // This actives the Listener within the simulation. try engine.rootObject.addChild(listener)
-
27:46 - Set Up a Volumetric Source
// Create an Icosahedron Mesh. let mesh = MDLMesh.newIcosahedron(withRadius: 0.0142, inwardNormals: false, allocator:nil) // Create a Shape from the Icosahedron Mesh. let shape = PHASEShape(engine: engine, mesh: mesh) // Create a Volumetric Source from the Shape. let source = PHASESource(engine: engine, shapes: [shape]) // Translate the Source 2 meters in front of the Listener and rotated back toward the Listener. var sourceTransform: simd_float4x4 sourceTransform.columns.0 = simd_make_float4(-1.0, 0.0, 0.0, 0.0) sourceTransform.columns.1 = simd_make_float4(0.0, 1.0, 0.0, 0.0) sourceTransform.columns.2 = simd_make_float4(0.0, 0.0, -1.0, 0.0) sourceTransform.columns.3 = simd_make_float4(0.0, 0.0, 2.0, 1.0) source.transform = sourceTransform; // Attach the Source to the Engine's Scene Graph. // This actives the Listener within the simulation. try engine.rootObject.addChild(source)
-
29:15 - Set Up an Occluder
// Create a Box Mesh. let boxMesh = MDLMesh.newBox(withDimensions: simd_make_float3(0.6096, 0.3048, 0.1016), segments: simd_uint3(repeating: 6), geometryType: .triangles, inwardNormals: false, allocator: nil) // Create a Shape from the Box Mesh. let boxShape = PHASEShape(engine: engine, mesh:boxMesh) // Create a Material. // In this case, we'll make it 'Cardboard'. let material = PHASEMaterial(engine: engine, preset: .cardboard) // Set the Material on the Shape. boxShape.elements[0].material = material // Create an Occluder from the Shape. let occluder = PHASEOccluder(engine: engine, shapes: [boxShape]) // Translate the Occluder 1 meter in front of the Listener and rotated back toward the Listener. // This puts the Occluder half way between the Source and Listener. var occluderTransform: simd_float4x4 occluderTransform.columns.0 = simd_make_float4(-1.0, 0.0, 0.0, 0.0) occluderTransform.columns.1 = simd_make_float4(0.0, 1.0, 0.0, 0.0) occluderTransform.columns.2 = simd_make_float4(0.0, 0.0, -1.0, 0.0) occluderTransform.columns.3 = simd_make_float4(0.0, 0.0, 1.0, 1.0) occluder.transform = occluderTransform // Attach the Occluder to the Engine's Scene Graph. // This actives the Occluder within the simulation. try engine.rootObject.addChild(occluder)
-
30:33 - Start a Spatial Sound Event
// Associate the Source and Listener with the Spatial Mixer in the Sound Event. let mixerParameters = PHASEMixerParameters() mixerParameters.addSpatialMixerParameters(identifier: spatialMixerDefinition.identifier, source: source, listener: listener) // Create a Sound Event from the built Sound Event Asset "drumEvent". let soundEvent = try PHASESoundEvent(engine: engine, assetIdentifier: "drumEvent", mixerParameters: mixerParameters)
-
31:28 - Example 1: Footstep on creaky wood
// Create a Sampler Node from "footstep_wood_clip_1" and hook it into a Channel Mixer. let footstep_wood_sampler_1 = PHASESamplerNodeDefinition(soundAssetIdentifier: "footstep_wood_clip_1", mixerDefinition: channelMixerDefinition)
-
31:54 - Example 2: Random footsteps on creaky wood
// Create a Sampler Node from "footstep_wood_clip_1" and hook it into a Channel Mixer. let footstep_wood_sampler_1 = PHASESamplerNodeDefinition(soundAssetIdentifier: "footstep_wood_clip_1", mixerDefinition: channelMixerDefinition) // Create a Sampler Node from "footstep_wood_clip_2" and hook it into a Channel Mixer. let footstep_wood_sampler_2 = PHASESamplerNodeDefinition(soundAssetIdentifier: "footstep_wood_clip_2", mixerDefinition: channelMixerDefinition) // Create a Random Node. // Add 'Footstep on Creaky Wood' Sampler Nodes as children of the Random Node. // Note that higher weights increase the likelihood of that child being chosen. let footstep_wood_random = PHASERandomNodeDefinition() footstep_wood_random.addSubtree(footstep_wood_sampler_1, weight: 2) footstep_wood_random.addSubtree(footstep_wood_sampler_2, weight: 1)
-
32:47 - Example 3: Random footsteps on creaky wood or soft gravel
// Create a Sampler Node from "footstep_gravel_clip_1" and hook it into a Channel Mixer. let footstep_gravel_sampler_1 = PHASESamplerNodeDefinition(soundAssetIdentifier: "footstep_gravel_clip_1", mixerDefinition: channelMixerDefinition) // Create a Sampler Node from "footstep_gravel_clip_2" and hook it into a Channel Mixer. let footstep_gravel_sampler_2 = PHASESamplerNodeDefinition(soundAssetIdentifier: "footstep_gravel_clip_2", mixerDefinition: channelMixerDefinition) // Create a Random Node. // Add 'Footstep on Soft Gravel' Sampler Nodes as children of the Random Node. // Note that higher weights increase the likelihood of that child being chosen. let footstep_gravel_random = PHASERandomNodeDefinition() footstep_gravel_random.addSubtree(footstep_gravel_sampler_1, weight: 2) footstep_gravel_random.addSubtree(footstep_gravel_sampler_2, weight: 1) // Create a Terrain String MetaParameter. // Set the default value to "creaky_wood". let terrain = PHASEStringMetaParameterDefinition(value: "creaky_wood") // Create a Terrain Switch Node. // Add 'Random Footstep on Creaky Wood' and 'Random Footstep on Soft Gravel' as Children. let terrain_switch = PHASESwitchNodeDefinition(switchMetaParameterDefinition: terrain) terrain_switch.addSubtree(footstep_wood_random, switchValue: "creaky_wood") terrain_switch.addSubtree(footstep_gravel_random, switchValue: "soft_gravel")
-
34:08 - Example 4: Random footsteps on changing terrain with a variably wet surface
// Create a Sampler Node from "splash_clip_1" and hook it into a Channel Mixer. let splash_sampler_1 = PHASESamplerNodeDefinition(soundAssetIdentifier: "splash_clip_1", mixerDefinition: channelMixerDefinition) // Create a Sampler Node from "splash_clip_2" and hook it into a Channel Mixer. let splash_sampler_2 = PHASESamplerNodeDefinition(soundAssetIdentifier: "splash_clip_2", mixerDefinition: channelMixerDefinition) // Create a Random Node. // Add 'Splash' Sampler Nodes as children of the Random Node. // Note that higher weights increase the likelihood of that child being chosen. let splash_random = PHASERandomNodeDefinition() splash_random.addSubtree(splash_sampler_1, weight: 9) splash_random.addSubtree(splash_sampler_2, weight: 7) // Create a Wetness Number MetaParameter. // The range is [0, 1], from dry to wet. The default value is 0.5. let wetness = PHASENumberMetaParameterDefinition(value: 0.5, minimum: 0, maximum: 1) // Create a 'Wetness' Blend Node that blends between dry and wet terrain. // Add 'Terrain' Switch Node and 'Splash' Random Node as children. // As you increase the wetness, the mix between the dry footsteps and splashes will change. let wetness_blend = PHASEBlendNodeDefinition(blendMetaParameterDefinition: wetness) wetness_blend.addRangeForInputValues(belowValue: 1, fullGainAtValue: 0, fadeCurveType: .linear, subtree: terrain_switch) wetness_blend.addRangeForInputValues(aboveValue: 0, fullGainAtValue: 1, fadeCurveType: .linear, subTree: splash_random)
-
// Create a Sampler Node from "gortex_clip_1" and hook it into a Channel Mixer. let noisy_clothing_sampler_1 = PHASESamplerNodeDefinition(soundAssetIdentifier: "gortex_clip_1", mixerDefinition: channelMixerDefinition) // Create a Sampler Node from "gortex_clip_2" and hook it into a Channel Mixer. let noisy_clothing_sampler_2 = PHASESamplerNodeDefinition(soundAssetIdentifier: "gortex_clip_2", mixerDefinition: channelMixerDefinition) // Create a Random Node. // Add 'Noisy Clothing' Sampler Nodes as children of the Random Node. // Note that higher weights increase the likelihood of that child being chosen. let noisy_clothing_random = PHASERandomNodeDefinition() noisy_clothing_random.addSubtree(noisy_clothing_sampler_1, weight: 3) noisy_clothing_random.addSubtree(noisy_clothing_sampler_2, weight: 5) // Create a Container Node. // Add 'Wetness' Blend Node and 'Noisy Clothing' Random Node as children. let actor_container = PHASEContainerNodeDefinition() actor_container.addSubtree(wetness_blend) actor_container.addSubtree(noisy_clothing_random)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。