大多数浏览器和
Developer App 均支持流媒体播放。
-
使用物理音频空间引擎 (PHASE) 探索几何感知音频
敬请探索几何感知音频如何帮助您为自己的 app 和游戏构建复杂且身临其境的交互式音频场景。了解 Apple 的空间音频 API:PHASE,并了解物理音频空间引擎 (PHASE) 是如何让声音始终与您的体验保持一致,从而帮助您在开发过程中创建空间音景和场景,而不是等到后期制作。我们将带您了解 API 及其类,包括来源、监听器、声学几何和材料,并介绍空间建模的概念。我们还将展示如何快速组合 PHASE 的基本构建块,开始为您的 app 或游戏构建集成音频体验。
资源
相关视频
WWDC23
WWDC22
-
下载
大家好 我叫巴拉斯 我来自Apple的核心音频团队 今天 我想向你们说明 用PHASE处理几何感知音频 我们会讨论为什么你们会想使用 新的PHASE框架 我们会向你们介绍这个框架提供的 一些功能 然后我会把镜头交给 我的同事大卫索尔 向你们深入说明概念 以及这个API的使用范例 让我们开始吧 音频是任何游戏体验的重要层面之一 耳机上的空间音频 把整体游戏体验提升到另一个层次 并让你觉得更加身历其境 在如今的游戏中 游戏引擎的各种子系统 例如物理现象 动画、视觉效果等等 全都跟彼此交互作用 并根据玩家的行动 推动游戏流程或故事线前进 不过 音频子系统的管理与驱动 通常跟其余子系统是分开的 它们有时也是通过中间件建构的 而中间件不一定会感知到模拟 音频资产会经过后制、预先烘焙 手动调谐来讲述一个 符合游戏视觉效果的音频故事 随着视觉效果进化 音频系统、音效设计 和相关资产都需要重新生成 来维持音频体验 在各种平台上的一致性 游戏开发时需要考虑 这种迭代开发过程 这通常会导致音频体验 滞后于游戏的视觉方面
为了提供更好的游戏音频体验 我们想要让音频系统 更靠近其他子系统 我们也想要让你们更轻松地写出 能够在所有支持设备上 提供一致的空间音频体验的 应用程序
我们现在就来说明 新的音频框架PHASE和它的功能 PHASE是一种新框架 将会让你能够 把几何信息提供给音频引擎 帮助你建构一套适合音效设计 事件驱动的音频播放系统 让你可以写出能够在所有支持设备上 自动提供一致的 空间音频体验 又能够与现有制作解决方案 与管道整合的应用程序 在我们开始进一步 了解PHASE之前 让我们先复习一套 常用的游戏音频工作流程 这是一个范本户外场景 有一个听者、一个音源 也就是一条流动的小溪 还有一个遮挡器 就是一座谷仓 遮挡器是场景中 可能减弱音源与听者之间 声音的对象 一般来说 你会在一片区域中 放置好几个点音源 随着听者移动 你必须使用各种技术 例如光线追踪 来决定点音源之间的 适当滤波混音比 并在它们之间手动混合 来提供良好的音频体验 在游戏开发的自然过程中 如果视觉场景改变 比如 范本场景中的谷仓 你必须更新及手动调谐音频体验 来配合视觉场景变化 假设你可以建构出的应用程序里 音频来源不是你需要根据场景 来处理和混音的点 而是音频系统能够自动为你处理 让声音从一片面积 或一个体积发出来 PHASE就是有这种功能 也就是引入体积音源 新框架提供的API让你能够把音源 作为几何形状传递给底层音频引擎 除了体积音源之外 你也可以把场景中的遮挡器 当作几何形状来传递 你也可以从一组预设中 选择声学材料属性 然后把它们附加到遮挡器上 PHASE框架也让你能够 根据应用程序的需求来设置 点音源的媒介传播和来源指向性 我们这里看到的例子是 一个户外场景 不过 如果你的应用程序有室内场景 你可以从预设库中 选择早期反射 和晚期混响的属性 只要你告诉框架各种音源 遮挡器、听者的位置 PHASE就会帮忙完成繁重的工作 并为你模拟场景中各种音源的 遮挡和扩散效果 既然现在你的应用程序的 音频系统是几何感知的 它就能随着游戏开发的进化 更快采用视觉场景变化
除了几何感知之外 PHASE还提供以事件为基础的 互动播放系统 声音事件是PHASE中 描述音频播放事件的 基本元件 它们概括了音频资产的 选择、混合和播放 声音事件的范围 从单次播放、循环播放等简单事件 到复杂序列都能涵盖在内 复杂序列的组织 像树一样 能在播放事件的子树之间 进行混合或切换 让我们来看一个播放脚步的简单例子 我们在这里有一个随机节点 会自动在三种砂砾上的 脚步声之中做选择 我们还可以有表现衣物摩擦的 另一个声音事件 这两个事件树 都能被嫁接到另一个树上 在这个例子指的是“近处” 这样就能播放衣物摩擦声 和脚步声的混音 我们还可以用另一个事件树 在这个例子指的是“远处” 来播放当角色在远处时的 一组不同声音 近处和远处事件树的混音 现在能根据游戏中的距离进行控制 你可以添加更多事件树 例如雪上或草上的脚步声 建构一个复杂的播放事件序列 这种序列能受到用户互动 或受到物理现象 及动画等子系统的触发
使用PHASE 声音可以 在一个简单的 通道配置中播放 或在一个 具有方向或位置的 3D空间中播放 或者作为 声音有方向但没有位置的环境床播放 底层引擎建构在 我们的空间音频渲染能力上 支持的iOS、macOS设备 以及Air Pods耳机系列 都已经提供这类能力 这让你建构的应用程序 能够在所有支持设备上 自动提供一致的空间音频体验 接下来 我要邀请大卫索尔 更深入介绍PHASE 并多加说明 概念和用于一些使用范例的API 大家好 我叫大卫索尔 我是PHASE的 系统建筑师与开发负责人 今天我要向你们说明 PHASE API 在这个环节 我会介绍一般概念 接着 我会展示一些使用范例 来帮助你们入门 PHASE API可以分成三大概念 引擎管理资产 节点控制播放 混音器控制空间化 PHASE引擎可以分解成 三大部分 资产注册表、场景图形、渲染状态 在引擎的生命周期中 你会使用引擎 来注册和注销资产 现在 PHASE支持注册声音资产 和声音事件资产 声音资产能从音档直接载入 或作为原始音频数据打包并嵌入 你自己的资产 然后直接载入引擎 声音事件资产是 一个以上的层次结构性节点的集合 这些节点控制声音播放 以及控制空间化的下游混音器 场景图形是参与模拟的 对象层次结构 这包括听者、音源、遮挡器 听者是代表你听到模拟时 在空间中所处位置的对象 音源是代表声音 发源处的对象 正如巴拉斯之前提到的 PHASE同时支持点音源 和体积音源 遮挡器这种对象代表了 模拟中会影响声音 在环境中传递的几何图形 遮挡器也被指定为影响 对象吸收与传递声音的材料 PHASE带有一个材料预设库 能被指派给遮挡器 来模拟从纸箱到玻璃窗 再到砖墙等一切材料 当你把对象添加到你的场景中 你将会把它们组织成 层次结构 并把它们 附加到引擎的根对象 不论是直接或间接附加都行 这会确保它们在每个画面 都会参与模拟 渲染状态管理播放声音事件 和音频输入输出 当你首次建立引擎时 音频输入输出是关闭的 这让你能够注册你的资产 建构你的场景图形、建立声音事件 并进行其他引擎操作 这一切都不需要运行音频输入输出 当你准备好播放声音事件 你可以开启引擎 这会从内部开启音频输入输出 同样地 当你结束播放声音事件时 你也可以停止引擎 这会停止音频输入输出 也会停止播放任何声音事件 PHASE的节点 控制音频内容的播放 节点是对象的层次结构式集合 会产生或控制音频播放 产生器节点会产生音频 它们一定是节点层次结构中的叶节点 控制器节点会设置 产生器在空间化之前 如何被选择、混合和参数化的逻辑 控制器节点一定是父节点 而且可以为复杂的音频设计场景 组织成层次结构 采样器节点是一种产生器节点 采样器会播放已注册的声音资产 只要建构了采样器节点 你就可以在上面 设置一些基本属性 让它正确播放 播放模式决定音频文件的播放方式 如果你把播放模式设置成单次播放 音频文件就会播放一次 然后自动停止 这能用在“即发即弃”场景中 例如触发音效 如果你把播放模式设置成循环播放 音频文件会持续播放 直到你明确停止采样器为止 剔除选项会告诉PHASE 当声音变得听不见时要做什么 如果你把剔除选项设置成终止 声音变得听不见时 就会自动停止 如果你把剔除选项设置成睡眠 声音变得听不见时就会停止渲染 变得听得见时又会再次开始渲染 这样一来 当音频由引擎剔除时 你就不需要 手动开启或停止音频 校准电平会设置声音的真实电平 以分贝声压级为单位 PHASE也支持四种控制器节点 包括随机、切换、混合和容器节点 随机节点会根据加权随机选项 来选择其中一个子节点 比如在这个案例中 下一次声音事件被触发时 左侧采样器跟右侧采样器 有4:1的比率被选中 切换节点会根据参数名称 在子节点之间调换 比如说 你可以把地形切换节点 从“嘎吱作响的木材” 改成“软质的砂砾” 下一次声音事件被触发时 它就会选择符合参数名称的采样器 混合节点会根据参数值 在子节点之间混合 比如说 你可以把一个湿度参数 指派给一个混合节点 这可以在干端的响亮脚步声 和安静溅水声 以及湿端的安静脚步声 和响亮溅水声之间混合 容器节点会同时播放所有子节点 比如你可以有一个 播放脚步声的采样器 还有一个播放衣物摩擦声的采样器 例如一件摩擦作响的 Gor-Tex夹克的声音 每一次容器节点被触发时 两个采样器都会同时播放
PHASE的混音器 控制音频内容的空间化 PHASE目前支持通道 环境、空间混音器 通道混音器会渲染音频 却不会有空间化 和环境音效 你可以把通道混音器用于 需要直接渲染到输出设备的 基于主干的常规内容 例如 立体声音乐或中心通道叙事对话 环境混音器会通过外部化来渲染音频 却不会使用距离建模或环境音效 当听者转头时 声音会持续从空间中相同的 相对位置传过来 你可以把环境混音器用于多通道内容 这种内容不会在环境中受到模拟 但仍然应该 听起来像是从空间中的某处 传过来一样 例如蟋蟀在大森林中 鸣叫的背景 空间混音器会进行完全空间化 当音源改变跟听者的相对位置时 你会听到在感知位置 音量、频率反应方面的变化 这些变化是基于平移 距离建模 指向性建模算法而产生的 除此之外 几何感知环境音效 也应用于音源和听者之间的路径 如果你戴着耳机 你也会 通过双耳滤波器的应用取得外部化 你可以把空间混音器用于 应该参与完全环境模拟的声音 空间混音器支持 两种独特的距离建模算法 你可以为随距离自然衰减 设置标准几何传播损耗 你也可以根据喜好 来增加或减少这种效应 举例来说 如果你想在远处 用麦克风进行对话 降低数值可能会很有用 在频谱的另一端 你可以添加随距离衰减的 完整分段弯曲段 比如你可以在范围的起点和终点 建购一组具有 自然距离衰减的分段 但在中间减少衰减效果 使重要对话在距离增加时也能被听见 对于点音源 空间混音器支持 两种不同的指向性建模算法 你可以把心形指向性建模 加入你的空间混音 利用一些简单的修正 你就可以用心形指向性模式 来模拟一名人类讲者 或者用超心形指向性模式 来模拟原声弦乐器的声音 你也可以添加圆锥形指向性建模 这种经典模式让你能够把指向性滤波 限制在特定旋转范围内 空间混音器也支持 以空间管道为基础的 几何感知环境音效 空间管道会选择要启用或禁用 环境音效 以及各自的发送电平 PHASE目前支持直接路径传递 早期反射和晚期混响 直接路径传递会渲染 音源和听者之间的 直接路径与遮挡路径 请注意在声音受到遮挡的情况下 有些能量会被材料吸收 而其他能量则会传递到 物体的另一面 早期反射会把强度修正与染色 同时提供给直接路径 这些通常是由墙壁和地板的 镜面反射构成的 在较大的空间内 它们也会把明显的回音 加入体验 晚期混响提供环境音频 它是漫射散射能量的密集堆积 结合成空间的 最终可听表现 除了提供关于房间大小 及形状的线索之外 它也为你提供一种包围感 现在我已经回顾了PHASE引擎 节点和混音器 背后的概念 是时候把这些概念 结合一些使用范例了 在这个环节 我会向你们说明播放音档 建构空间音频体验 以及建构行为声音事件 这三个重要领域会让你 对PHASE的功能有广泛的理解 还会在开头提供稍微的介绍 然后在中段和结尾深入探讨 更有趣的功能 一开始 我会向你们展示如何播放音档 首先 我们来创建 一个PHASE引擎实例 接着 我会取回音档的URL 并使用PHASE注册声音资产 我会称呼它“鼓声” 这样我之后就能指称它 我在这里会创建一个引擎 然后用代码注册一个声音资产 首先 我会在自动更新模式下 创建一个PHASE引擎实例 这是启动和运行的首选模式 所以我在这里用它来演示简单播放 请注意 当游戏需要更精确地 与画面更新同步时 手动模式是解决方法 请查看文件了解更多细节 接着 我会取回一个储存在 应用程序包的音频文件URL 这是一个单声道24位 48kHz WAV文件 带有一段准备好的鼓声循环样本 当我用引擎注册这个声音资产时 我会提供 一些额外的参数 我会给这个声音资产 取一个独特的名字 这样我之后就能指称它 我会指定声音资产内的音频数据 应该被预载到常驻内存 而不是把它实时串流到内存 这应该没问题 因为鼓声循环相当短 而且我可能想连续 播放几次 我也选择对声音资产在输出设备上的 校准响度进行标准化 一般建议将输入标准化 只要我们把它指定给一个采样器 并设置它的目标输出电平 我们就能更容易混合内容 现在我已经用引擎 注册了一个声音资产 我会建构一个声音事件资产 首先 我会从一个通道布局 创建一个通道混音器 然后我会创建一个采样器节点 采样器节点会采用 已注册声音资产的名称 以及对下游通道混音器的引用 接下来 我会在采样器节点上 设置一些基本属性 使它正确播放 播放模式会设置 采样器是否会循环播放音档 而校准电平会设置 混音中采样器的感知响度 现在我已经将采样器节点的 输出连接到 通道混音器的输出 并设置一些基础参数 是时候用引擎注册声音事件资产了 在这个例子中 我会用drumEvent这个名称 来注册声音事件资产 我之后会用这个名称来指称它 在这里我会用代码注册 一个声音事件资产 我会从一个单声道通到布局标签 创建一个通道布局 然后我会用这个单声道通道布局 初始化通道混音器 接着我会创建采样器节点 并传入名称drums 这代表我之前用引擎注册的 单声道鼓声资产 采样器节点将被传送到 下游通道混音器 我会把播放模式设定成循环播放 这会确保声音持续播放 直到我明确从代码停止播放为止 我会把校准模式设置成相对声压级 把电平设置成0分贝 这将确保体验的 舒适聆听水平 最后我会用引擎 注册声音事件资产 传入drumEvent这个名称 这样我之后开始 创建声音事件来播放时 就能指称它 声音事件资产完成注册后 我就可以创建一个实例并开始播放 我会做的第一件事 是从名称是drumEvent的 已注册声音事件资产 创建一个声音事件 现在我有一个声音事件 我就会启动引擎 这会启动音频输入输出 我就可以听到 输出设备播放音频 最后我会启动声音事件 此时 载入的声音资产 会通过采样器播放 传送到通道混音器 重新映射到当前的输出格式 然后通过输出设备播放 这里我会用代码启动一个声音事件 声音事件资产是从 已注册声音资产的名称建立的 我在这里传入drumEvent 我会开始启动引擎 这会启动音频输入输出 也会启动声音事件
只要我播放完声音事件 我就能清除引擎 首先我会停止声音事件 然后我会停止引擎 这会停止音频输入输出 也会停止任何播放中的声音事件 接着我会注销 名称为drumEvent的声音事件资产 并注销名称为drums的声音资产 最后我会销毁引擎 这里我会用代码进行清除 首先 我听完鼓声循环后 就停止声音事件 然后我会停止引擎 这会在内部停止 音频输入输出 接着 我会注销名称是drumEvent的 声音事件资产 并注销名称是drums的声音资产 最后 我会销毁引擎
现在我已经介绍完基础功能 我会向你们展示如何在PHASE 建构简单的空间音频体验 我们会介绍的主题包括空间混音器 体积音源、遮挡器 首先我要做的是用引擎注册 一个声音事件资产 在这个范例 我会先从已经注册鼓声 声音事件的引擎开始 从这里 我会升级混音 从简单的、基于通道的播放 到完全空间化 我首先会做的是建立一个空间管道 来选择性地把不同环境音效 应用到我的音源 然后我会从空间管道 创建一个空间混音器 建立之后 我会在空间混音器上 设置一些基本属性 使它正确播放 在这个例子中 我会设置距离模型 来控制随距离的电平衰减 我也会设置指向性模型 来控制依据音源相对于听者 之间角度的电平衰减 然后我会创建一个采样器节点 采样器节点会采用 已注册声音资产的名称 以及对下游空间混音器的引用 接着我会在采样器节点 设置一些基本属性 使它正确播放 除了播放模式 和校准电平之外 我也会在这里设置剔除选项 这会告诉PHASE 当采样器变得无法听见时该做什么 现在我已经把采样器节点的输出 连接到空间混音器的输入 并设置一些基本参数 是时候用引擎注册声音事件资产了 我会使用跟之前一样的名称 这里我会用代码 创建一个声音事件资产 首先我会创建一个 spatialPipeline 来渲染 .directPathTransmission 和.lateReverb 我也会继续设置 .lateReverb .sendLevel 来控制指向与混响比 并为晚期混响模拟 选择一个.mediumRoom预设 然后我会用spatialPipeline 创建一个空间混音器 接着我会创建一个自然声 GeometricSpreadingDistanceModel 并将它指定给空间混音器 我会把cullDistance设置为10米 如果音源比这个距离还远 我就会想要从混音器自动剔除它 我会稍微调整rolloffFactor 不再强调距离衰减效果 然后我会创建一个采样器节点 把“鼓声”这个名称传给它 这代表我之前用引擎注册的 单声道鼓声声音资产 我会把playbackMode 设置成.looping 也会把校准模式 设置成.relativeSpl 把电平设置成+12分贝来增加 采样器的输出电平 我还会把cullOption设置成睡眠 最后 我会用引擎注册 soundEventAsset 把drumEvent这个名称传过去 这样之后当我开始创建声音事件时 就可以指称它 现在我有一个用引擎注册的 声音事件资产 我需要为模拟创建一个场景 这包含创建一个听者、音源 和遮挡器 在这个例子中 我会在音源和听者之间 放置遮挡器 首先我会创建一个听者 然后我会设置它的转换 只要我准备好让听者 在场景图形里活动 我就会把它附加到 引擎的根对象或其子对象 这里我会用代码设置听者 首先我会创建一个听者 然后我会设置它的转换 在这个例子中 我会把听者设置成不旋转的原点 最后我会把听者附加引擎的根对象
现在我们来设置一个体积音源 首先我会从网格创建一个音源形状 然后我会从这个形状创建一个音源 这内建了一个体积音源 然后我会设置它的转换 当我准备好让音源 在场景图形内启动 我会把它附加到引擎的根对象 或其子对象之一 这里我会用代码设置一个体积音源 首先我会创建一个二十面体网格 然后把它缩放到 差不多一个 HomePod Mini的大小 然后我会从网格创建一个形状 这种形状能重复使用 来建立体积音源的 多个例子 比如说 我可以在 共享相同网格的模拟中 放置多个HomePod Mini
接着我会从这种形状 创建一个体积音源 请注意 我也可以使用一个 不把形状当作输入的 初始值设定项版本 来创建一个简单点音源 然后我会设置它的转换 我会在听者前2米处平移音源 然后把它旋转回听者方向 这样它们就会面对彼此 最后我会把音源附加到引擎的根对象
现在 我们来设置一个遮挡器 首先我会从网格创建一个形状
然后我会创建一个硬纸板材料 把它指派给那个形状 现在这个形状 有几何形状和关联材料 接着我会从这个形状创建一个遮挡器 然后我会设置它的转换 当我准备好在场景图形中 让遮挡器启动时 我就会把它附加在引擎的根对象 或其子对象之一 这里我会用代码设置遮挡器 首先我会创建一个boxMesh 然后相应地缩放它的尺寸 接着我会从这个网格创建一个形状 这个形状可以重复利用来建立 遮挡器的多个例子 比如说 我可以在 共享相同网格的模拟中 放置多个箱子 接着 我会从纸箱预设创建一个材料 并把这个材料指派给形状
然后我会从这个形状创建一个遮挡器 接着我会设置它的转换 我会把它在听者前1米处平移遮挡器 再把它旋转回听者方向 这样它们就会面对彼此 这会把遮挡器放在音源和听者 正中间的位置 最后我会把遮挡器 附加在引擎的根对象
此时 我有一个遮挡器 位于音源和听者正中间的场景
接着 我会从我们注册的 声音事件资产中 创建一个声音事件 并把它关联到 场景图形中的音源和听者 当我启动声音事件 我会听到 从纸箱对面一个小型体积音源 播放的受遮挡鼓声循环 这里我会用代码启动声音事件 首先 我会把音源和听者 跟声音事件的空间混音器关联在一起 然后我会从名称为drumEvent的 已注册声音资产 创建一个soundEvent 其余步骤就跟之前一样 先确保引擎在运行中 然后启动声音事件 现在我已经说明了空间音频 我会向你们展示如何建构 复杂的声音事件 声音事件能被组织成 互动式声音设计的行为层次结构 在这个环节 我会向你们展示连贯性范例 它们建构在每一种声音事件节点上 用来创建最终声音事件 这里我们将模拟一个穿着吵杂的 Gore-Tex夹克的行动者 在表面湿度可变的 不同类型地形上行走 首先我会创建一个采样器节点 这个节点播放 在嘎吱作响的木材上的脚步声 在代码中 我会用一个名称是 “footstep_wood_clip_1”的 已注册声音事件 创建一个采样器节点 在这个例子里 这个节点和其他节点都会在 单一一个预建的通道混合器上播放 现在我会加入一些随机性 我会用两个子采样器节点 创建一个随机节点 两个子采样器节点播放嘎吱作响的 木材上稍微不同的脚步声范本 在代码中 我会创建两个采样器节点 第一个使用称为 “footstep_wood_clip_1”的 已注册声音资产 第二个使用称为 “footstep_wood_clip_2”的 已注册声音资产 接着 我会创建一个随机节点 并把采样器节点当作子节点添加进去 请注意 加权因数 被应用在每个子节点 目的是控制那个子节点 在后续迭代中被选中的机率 在这个例子中 第一个子节点 被选中的机率是第二个子节点的两倍
接着我会添加一个地形切换 我会创建一个切换节点 以及两个当作子节点的随机节点 在这个例子中 第二个随机节点 会播放砂砾上的随机脚步声 而不是在木材上的随机脚步声 我会使用一个地形参数来控制切换 在代码中 我会创建两个采样器节点 第一个使用称为 “footstep_gravel_clip_1”的 已注册声音资产 第二个 使用称为 “footstep_gravel_clip_2”的 已注册声音资产 然后我会创建一个随机节点 并添加采样器节点作为子节点 接着我会创建一个地形参数 默认值会是“creaky_wood” 然后我会创建一个切换节点 这个节点受到地形参数的控制 我会加入两个子节点 分别是木材随机节点 和砂砾随机节点 如果我把参数设置为 “creaky_wood” 它就会选择木材随机节点 同样地 如果我把参数设置为 “soft_gravel” 它就会选择砂砾随机节点
接着我会添加一个湿度混合 我会创建一个混合节点 而地形切换节点 和随机溅水节点作为子节点 新的随机溅水节点 会在行动者踏步时播放随机溅水声 而地形切换则决定 行动者的脚是走在嘎吱作响的木材上 还是软软的砂砾上 干燥脚步声和溅水声之间的 混合取决于湿度参数 从完全干燥 也就是响亮脚步声和没有溅水声 到完全潮湿 也就是安静脚步声 和响亮溅水声 在代码中 我会创建两个采样器节点 第一个使用称为 “splash_clip_1”的 已注册声音资产 第二个使用称为 “splash_clip_2”的 已注册声音资产 然后我会注册一个随机节点 并添加采样器节点 作为子节点 接着我会创建一个湿度参数 范围会是0到1 默认值会是0.5 请注意 我可以把参数设置成 我的游戏支持的任何值和范围 然后我会创建一个混合节点 这个节点由湿度参数控制 我会添加两个子节点 分别是地形切换节点 和随机溅水声节点 如果我把参数设置成0 我只会听到嘎吱作响的木材 或砂砾上的干燥脚步声 取决于地形 当我把湿度从0添加到1时 我会把伴随每个脚步的 溅水声响度增加 模拟潮湿的地形
最后我会创建一个容器节点 而湿度混合节点 和随机吵杂衣物节点作为子节点 新的吵杂衣物节点会播放 行动者在不同湿度的 变化地形上踏步时 Gore-Tex夹克发出的摩擦声 当这个最终节点层次结构完成时 我就有一套代表行动者在场景中 行走的完整表示 每当行动者走了一步 我就会听到夹克的摩擦声 以及嘎吱作响的木材上 或软软的砂砾上的脚步声 依据地形参数来决定 除此之外 我也或多或少会听到 伴随每一步而来的溅水声 依据湿度参数来决定 在代码中 我会创建两个采样器节点 第一个使用称为 “gortex_clip_1”的 声音资产 第二个使用称为 “gortex_clip_2”的 声音资产 然后我会创建一个随机节点 并添加采样器节点 当作子节点 最后 我会创建一个 actor_container节点 我会添加两个子节点 分别是wetness_blend节点 和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)
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。