WWDC Quick Look 💓 By SwiftGGTeam
Discover geometry-aware audio with the Physical Audio Spatialization Engine (PHASE)

Discover geometry-aware audio with the Physical Audio Spatialization Engine (PHASE)

元の動画を見る

ハイライト

PHASE フレームワークは、ジオメトリック情報をオーディオ エンジンに導入し、ボリューム音源、オクルージョン、マテリアル プロパティをサポートし、シーンのジオメトリに基づいて音が自動的に伝播および減衰できるようにし、複数の点音源を手動でミックスする必要を排除します。

主な内容

ゲームオーディオの長期的な問題点

従来のゲーム開発では、オーディオ サブシステムは、物理学、アニメーション、視覚効果などのサブシステムとは別に実行されます。オーディオは通常、サードパーティのミドルウェアを通じて生成され、ゲームのシミュレーション状態を認識しません。サウンド デザイナーは、オーディオ アセットをプリベイクし、サウンド効果がゲーム画面と一致するように手動で調整する必要があります。視覚的なシーンが変化したら、一貫性を維持するためにオーディオ システムを再加工する必要があります。(01:00)

空間オーディオの観点から見ると、従来のアプローチは、シーン内に複数の点音源を配置し、レイ トレーシングなどの技術を通じてフィルタリングと混合比率を決定し、各音源を手動で混合することです。シーンが変わると、この調整プロセスを最初からやり直す必要があります。(02:40)

PHASE ゲームオーディオを変更する方法

PHASE (Physical Audio Spatialization Engine) は、オーディオ システムを他のサブシステムと緊密に接続する新しい空間オーディオ フレームワークです。(01:36)

ボリューム音源。PHASE を使用すると、音源を個々の点ではなく幾何学的形状 (球、立方体、メッシュなど) として定義できます。オーディオ エンジンはボリューム内での音の広がりを自動的に管理するため、開発者が複数のポイント ソースを手動でミックスする必要がなくなります。(02:27)

幾何学的オクルージョン。オクルージョン (壁、障害物など) は幾何学的形状として定義することもでき、音響材料の特性 (木、金属、ガラスなど) を割り当てることもできます。PHASE は、音が障害物によってどのように減衰され、フィルタリングされるかを自動的に計算します。(02:38)

デバイスの一貫性。PHASE は、サポートされているすべての Apple デバイスにわたって一貫した空間オーディオ エクスペリエンスを提供し、さまざまなプラットフォームに合わせて手動で調整する必要をなくします。(02:07)

##詳細

PHASE のコアコンセプト

PHASE フレームワークは、次の中心概念を中心に構築されています。

  • エンジン: オーディオ エンジンのコア オブジェクトであり、オーディオ セッション全体を管理します。
  • サウンド アセット: エンジンに登録できるオーディオ ファイル リソース
  • サウンド イベント: サウンド イベント アセットによって定義された再生可能なサウンド イベント
  • ソース: ボリューム形状として定義できる音源。
  • リスナー: プレイヤーまたはユーザーの耳の位置と方向を表すリスナー
  • オクルーダー: オクルーダー、音の伝播をブロックするジオメトリ
  • 材質: 障害物の吸音特性を定義する吸音材。
  • 空間パイプライン: ダイレクトパス伝送と残響を含む空間処理パイプライン

PHASE エンジンの作成

PHASE エンジンを作成し、オーディオ リソースを登録します: (18:31)

import PHASE

// 创建自动更新模式的引擎
let engine = PHASEEngine(updateMode: .automatic)

// 获取应用 Bundle 中的音频文件 URL
let audioFileUrl = Bundle.main.url(forResource: "DrumLoop_24_48_Mono",
                                    withExtension: "wav")!

// 注册音频文件资源
// 命名为 "drums",加载到常驻内存,应用动态归一化准备播放
let soundAsset = try engine.assetRegistry.registerSoundAsset(
    url: audioFileUrl,
    identifier: "drums",
    assetType: .resident,
    channelLayout: nil,
    normalizationMode: .dynamic
)

キーポイント:

  • updateMode: .automaticエンジンが更新を自動的に管理することを示し、ほとんどのシナリオに適しています
  • .resident頻繁に再生されるサウンドに適した常駐メモリにオーディオをロードするタイプ
  • normalizationMode: .dynamic動的正規化を適用して、再生中に一貫した音量を確保します
  • identifierリソースの一意の識別子です。その後、リソースを通じて参照されます。

サウンド イベント アセットの作成

サウンド イベント アセットは、サンプラーからミキサーまでのサウンド処理フローを定義します: (20:47)

// 从单声道布局标签创建声道布局
let channelLayout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_Mono)!

// 从声道布局创建声道混音器
let channelMixerDefinition = PHASEChannelMixerDefinition(channelLayout: channelLayout)

// 创建采样器节点,连接到下方的声道混音器
let samplerNodeDefinition = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "drums",
    mixerDefinition: channelMixerDefinition
)

// 设置采样器的播放模式为循环
samplerNodeDefinition.playbackMode = .looping

// 设置校准模式为相对声压级,电平为 0 dB
samplerNodeDefinition.setCalibrationMode(.relativeSpl, level: 0)

// 注册声音事件资源,命名为 "drumEvent"
let soundEventAsset = try engine.assetRegistry.registerSoundEventAsset(
    rootNode: samplerNodeDefinition,
    identifier: "drumEvent"
)

キーポイント:

  • PHASEChannelMixerDefinitionオーディオを出力チャンネルにミックスする方法を定義します
  • PHASESamplerNodeDefinitionオーディオファイルを再生するノードです
  • .loopingBGM や環境音に適したオーディオをループ再生します。
  • .relativeSpl校正モードは相対的な音圧レベルを表し、0 dB が基準レベルです
  • rootNodeノード ツリーのルートであり、複雑なオーディオ処理グラフを構築できます

再生の開始と停止

サウンド イベントを作成し、エンジンを開始します: (22:21)

// 从 Sound Event Asset 创建 Sound Event
let soundEvent = try PHASESoundEvent(engine: engine,
                                      assetIdentifier: "drumEvent")

// 启动引擎
// 这会内部启动音频 IO 线程
try engine.start()

// 启动声音事件
try soundEvent.start()

リソースのクリーンアップ: (23:05)

// 停止并使声音事件无效
soundEvent.stopAndInvalidate()

// 停止引擎
// 这会内部停止音频 IO 线程
engine.stop()

// 注销声音事件资源
engine.assetRegistry.unregisterAsset(identifier: "drumEvent", completionHandler: nil)

// 注销音频文件
engine.assetRegistry.unregisterAsset(identifier: "drums", completionHandler: nil)

// 销毁引擎
engine = nil

キーポイント:

  • start()オーディオ IO スレッドを開始し、オーディオ処理を開始します。
  • stop()オーディオ IO スレッドを停止します
  • stopAndInvalidate()再生を停止し、イベント リソースを解放します。
  • クリーニング時には逆の順序でリソースの登録を解除します

空間パイプライン構成

空間化されたパイプラインを作成して空間オーディオ効果を実現します: (25:14)

// 创建空间化管线
let spatialPipelineOptions: PHASESpatialPipeline.Options = [
    .directPathTransmission,
    .lateReverb
]
let spatialPipeline = PHASESpatialPipeline(options: spatialPipelineOptions)!
spatialPipeline.entries[PHASESpatialCategory.lateReverb]!.sendLevel = 0.1
engine.defaultReverbPreset = .mediumRoom

// 创建空间混音器
let spatialMixerDefinition = PHASESpatialMixerDefinition(spatialPipeline: spatialPipeline)

// 设置距离模型参数
let distanceModelParameters = PHASEGeometricSpreadingDistanceModelParameters()
distanceModelParameters.fadeOutParameters = PHASEDistanceModelFadeOutParameters(
    cullDistance: 10.0
)
distanceModelParameters.rolloffFactor = 0.25
spatialMixerDefinition.distanceModelParameters = distanceModelParameters

// 创建采样器节点,连接到空间混音器
let samplerNodeDefinition = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "drums",
    mixerDefinition: spatialMixerDefinition
)

// 设置循环播放
samplerNodeDefinition.playbackMode = .looping

// 设置校准电平为 12 dB
samplerNodeDefinition.setCalibrationMode(.relativeSpl, level: 12)

// 设置剔除选项为休眠
samplerNodeDefinition.cullOption = .sleepWakeAtRealtimeOffset

// 注册声音事件
let soundEventAsset = try engine.assetRegistry.registerSoundEventAsset(
    rootNode: samplerNodeDefinition,
    identifier: "drumEvent"
)

キーポイント:

  • .directPathTransmissionダイレクトパス転送を有効にする
  • .lateReverbポストリバーブを有効にする
  • sendLevelリバーブに送信される信号のレベルを制御します。
  • cullDistance除去距離です。それを超えるとサウンドの再生が停止します。
  • rolloffFactor距離減衰の傾きを制御します
  • .sleepWakeAtRealtimeOffset遠くの音源をスリープ状態にし、近づくと自動的に起動します

リスナーと音源を設定する

リスナーを作成します: (27:05)

// 创建听者
let listener = PHASEListener(engine: engine)

// 设置听者的变换矩阵为原点,无旋转
listener.transform = matrix_identity_float4x4

// 将听者附加到引擎场景图的根对象
// 这会在模拟中激活听者
try engine.rootObject.addChild(listener)

ボリューム音源を作成します: (27:46)

import ModelIO

// 创建二十面体网格
let mesh = MDLMesh.newIcosahedron(withRadius: 0.0142,
                                   inwardNormals: false,
                                   allocator: nil)

// 从网格创建形状
let shape = PHASEShape(engine: engine, mesh: mesh)

// 从形状创建体积声源
let source = PHASESource(engine: engine, shapes: [shape])

// 将声源移动到听者前方 2 米,并旋转回朝向听者
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

// 将声源附加到引擎的场景图
try engine.rootObject.addChild(source)

キーポイント:

  • PHASEListenerリスナーの位置と向きを表します
  • PHASESourceは音源です。1 つ以上を使用できます。PHASEShapeボリュームを定義する
  • transform位置、回転、スケールを含む 4x4 変換行列です
  • 空間計算に参加するには、音源とリスナーの両方をシーングラフに追加する必要があります

オクルージョンを設定する

オクルーダーとマテリアルを作成する: (29:15)

// 创建盒子网格
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
)

// 从网格创建形状
let boxShape = PHASEShape(engine: engine, mesh: boxMesh)

// 创建材质
// 这里我们使用纸板预设
let material = PHASEMaterial(engine: engine, preset: .cardboard)

// 在形状上设置材质
boxShape.elements[0].material = material

// 从形状创建遮挡物
let occluder = PHASEOccluder(engine: engine, shapes: [boxShape])

// 将遮挡物移动到听者前方 1 米
// 这将遮挡物放在声源和听者中间
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

// 将遮挡物附加到引擎的场景图
try engine.rootObject.addChild(occluder)

キーポイント:

  • PHASEOccluder音の伝播をブロックするのは幾何学形状です。
  • PHASEMaterialオクルージョンの音響特性を定義する
  • デフォルトのマテリアルには以下が含まれます.cardboard.brick.concrete.glass.metal.wood待って
  • 素材は、遮断された音の減衰とスペクトル特性に影響を与えます。

空間音響イベントの開始

音源とリスナーを空間ミキサーに関連付けます: (30:33)

// 将声源和听者与空间混音器关联
let mixerParameters = PHASEMixerParameters()
mixerParameters.addSpatialMixerParameters(
    identifier: spatialMixerDefinition.identifier,
    source: source,
    listener: listener
)

// 创建声音事件
let soundEvent = try PHASESoundEvent(
    engine: engine,
    assetIdentifier: "drumEvent",
    mixerParameters: mixerParameters
)

キーポイント:

  • PHASEMixerParametersミキサーパラメータを定義する
  • addSpatialMixerParameters音源とリスナーを空間ミキサーに関連付けます
  • サウンドイベントは、音源とリスナーの相対位置に基づいて空間効果を計算します。

高度な例: 足音システム

ランダム化、地形の切り替え、湿度、衣服の摩擦音をサポートする複雑な足音サウンド システムの構築を示します。(31:28)

例 1: モノラルの足音

let footstep_wood_sampler_1 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "footstep_wood_clip_1",
    mixerDefinition: channelMixerDefinition
)

例 2: 足音をランダム化する

// 创建两个采样器节点
let footstep_wood_sampler_1 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "footstep_wood_clip_1",
    mixerDefinition: channelMixerDefinition
)
let footstep_wood_sampler_2 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "footstep_wood_clip_2",
    mixerDefinition: channelMixerDefinition
)

// 创建随机节点
// 权重越高,被选中的概率越大
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)

キーポイント:

  • PHASERandomNodeDefinition再生する子ノードをランダムに選択します
  • weightパラメータは選択確率を制御します
  • ランダム化により繰り返しが回避され、リアリズムが向上します

例 3: 地形の切り替え

// 创建碎石脚步声随机节点
let footstep_gravel_sampler_1 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "footstep_gravel_clip_1",
    mixerDefinition: channelMixerDefinition
)
let footstep_gravel_sampler_2 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "footstep_gravel_clip_2",
    mixerDefinition: channelMixerDefinition
)

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)

// 创建地形字符串元参数
let terrain = PHASEStringMetaParameterDefinition(value: "creaky_wood")

// 创建地形切换节点
let terrain_switch = PHASESwitchNodeDefinition(
    switchMetaParameterDefinition: terrain
)
terrain_switch.addSubtree(footstep_wood_random, switchValue: "creaky_wood")
terrain_switch.addSubtree(footstep_gravel_random, switchValue: "soft_gravel")

キーポイント:

  • PHASEStringMetaParameterDefinition文字列メタパラメータを定義する
  • PHASESwitchNodeDefinitionメタパラメータ値に基づいて再生するサブツリーを選択します
  • 地形サウンドはメタパラメータを変更することで実行時に切り替えることができます

例 4: 湿度混合

// 创建水声随机节点
let splash_sampler_1 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "splash_clip_1",
    mixerDefinition: channelMixerDefinition
)
let splash_sampler_2 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "splash_clip_2",
    mixerDefinition: channelMixerDefinition
)

let splash_random = PHASERandomNodeDefinition()
splash_random.addSubtree(splash_sampler_1, weight: 9)
splash_random.addSubtree(splash_sampler_2, weight: 7)

// 创建湿度数字元参数
let wetness = PHASENumberMetaParameterDefinition(
    value: 0.5,
    minimum: 0,
    maximum: 1
)

// 创建湿度混合节点
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
)

キーポイント:

  • PHASENumberMetaParameterDefinition数値要素パラメータを定義する
  • PHASEBlendNodeDefinitionメタパラメータ値に基づいて複数の子ノードをブレンドする
  • 湿度 0 (乾燥) から 1 (完全に湿った)、乾燥した足音と水音の混合

例 5: 衣服の摩擦音を追加する

// 创建衣物摩擦声随机节点
let noisy_clothing_sampler_1 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "gortex_clip_1",
    mixerDefinition: channelMixerDefinition
)
let noisy_clothing_sampler_2 = PHASESamplerNodeDefinition(
    soundAssetIdentifier: "gortex_clip_2",
    mixerDefinition: channelMixerDefinition
)

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)

// 创建容器节点
let actor_container = PHASEContainerNodeDefinition()
actor_container.addSubtree(wetness_blend)
actor_container.addSubtree(noisy_clothing_random)

キーポイント:

  • PHASEContainerNodeDefinition複数の子ノードを同時に再生できるコンテナ ノード
  • 地形の足音と衣服の摩擦音を同時に再生します
  • ランダム化、地形切り替え、湿度、衣服の音をサポートする完全なサウンドシステムを構築

重要ポイント

  • 内容: ゲーム用の動的な足音システムを構築します

  • 実行する価値がある理由: PHASE のランダム ノード、スイッチング ノード、ハイブリッド ノードは、地形、湿度、その他の条件に応じて自動的に調整される複雑なダイナミック サウンド システムを構築できます。

  • 開始方法: を使用して複数のサウンド サンプラーを作成します。PHASERandomNodeDefinitionランダム化して使用PHASESwitchNodeDefinition地形を切り替えるには、次を使用しますPHASEBlendNodeDefinitionさまざまな条件でサウンドをミックスする

  • 内容: リアルなオクルージョンとリバーブ効果を実現します。

  • 実行する価値がある理由: PHASE は、幾何学的オクルージョンとマテリアル属性をサポートしています。音は壁によって遮られ、材質に応じて異なる減衰特性が得られ、没入感が向上します。

  • 開始方法: を使用しますPHASEOccluderシーン内のオクルージョンを定義し、オクルージョンごとに指定しますPHASEMaterial、エンジンのリバーブプリセットを次のように設定します。.mediumRoomまたは.largeHall

  • やるべきこと: 環境音響効果を実現するためにボリューム音源を構築する

  • 実行する価値がある理由: 従来の点音源では、広い領域のサウンドをシミュレートするために複数の点を手動で混合する必要があります。 PHASE のボリューム音源は、幾何学的な物体からの音の伝播を自動的に管理します。

  • 開始方法: を使用しますMDLMesh球状または立方体メッシュを作成するには、次を使用します。PHASEShapeパッケージ、渡すPHASESourceボリューム音源として

  • 内容: 距離を考慮した音声カリングを実装します。

  • 実行する価値がある理由: 遠くの音を再生する必要はありません。それらを排除すると、CPU リソースが節約され、パフォーマンスが向上します。

  • 開始方法: 設定PHASEGeometricSpreadingDistanceModelParameterscullDistance、サンプラーの設定cullOptionのために.sleepWakeAtRealtimeOffset

  • 内容: 状況に応じた空間オーディオ体験を作成します。

  • 実行する価値がある理由: PHASE は、シーン グラフ内の音源、リスナー、オクルージョン オブジェクトを統合し、リスナーまたは音源を移動したときの空間効果を自動的に計算します。

  • 開始方法: 作成するPHASEListenerそして複数のPHASESource、エンジンのシーン グラフに追加され、そのグラフが更新されます。transform財産

関連セッション

コメント

GitHub Issues · utterances