A-Frame:如何提供声音淡出以消除声音终止时的音频点击
Posted
技术标签:
【中文标题】A-Frame:如何提供声音淡出以消除声音终止时的音频点击【英文标题】:A-Frame: How to provide sound fadeout to eliminate audio click upon sound termination 【发布时间】:2017-10-06 10:07:48 【问题描述】:A-frame 通过其<sound>
component 提供易于使用和强大的音频功能。
在为我的游戏(正在进行中)尝试了各种声音选项(例如原生 html5)后,我得出结论,A 帧声音是最佳选择,因为它会自动提供空间化声音(例如,随着头部旋转而变化) ,以及当您靠近声源时强度会发生变化——这些东西会增加 VR 的存在感,而所有这些都是为了定义一个简单的 html 标记。
不幸的是,A-frame 没有提供淡出实用程序来在停止时使声音逐渐变细,因此可能会在某些波形上产生明显可听且令人讨厌的咔嗒声,尤其是。长度可变且波形本身不逐渐变细的声音(例如,宇宙飞船的推力)。这是带有计算机音频的well known problem 。
我能够找到一些 html5 audio solutions 和一个非常好的 three.js 音频 three.js audio solution,但我找不到特定于 A-frame 的。
在 A 帧中逐渐减小声音以减少/消除这种咔嗒声的最佳方法是什么?
【问题讨论】:
【参考方案1】:简介
A-frame sound
音频封装了 three.js positional audio API,而后者又封装了原生 html 5 音频。大多数解决方案都是为纯 html5 或纯 three.js 量身定制的。由于 A-frame 是两种 api 的混合体,因此所提供的解决方案都不适用于 A-frame。
在两次错误的开始之后,我发现了tween.js,它不仅是A-frame内置的(甚至不必下载库),而且是一个有用的API知道用于其他形式的计算机动画。我在这里提供主要解决方案以及a plunker,希望其他人能找到有用的东西。
请注意,对于子弹发射等短促的爆裂声,您无需执行此操作。这些声音有一个固定的生命周期,所以大概是谁创造了波形,一定要确保它们逐渐变细。另外,我只处理淡出,而不是淡入,因为我需要的声音只有淡出问题。一般的解决方案也包括淡入淡出。
解决方案
1) 我们从创建一个真实的基本场景开始,我们可以在其中播放音频:
<a-scene>
<a-assets>
<audio id="space-rumble" src="https://raw.githubusercontent.com/vt5491/public/master/assets/sounds/space-rumble.ogg" type="audio/ogg"></audio>
crossorigin="anonymous"
type="audio/ogg"></audio>
</a-assets>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"
sound="src: #space-rumble; volume: 0.9"
></a-box>
</a-scene>
此解决方案中的立方体和场景实际上只是占位符 - 您无需进入 VR 模式即可单击按钮并测试声音。
2) 代码提供了三个按钮:一个用于启动声音,一个用于使用 A 帧默认设置“硬”停止它,第三个用于“轻松”停止它,使用补间将其逐渐减小到零。第四个输入允许您改变锥度时间。虽然它可能看起来像很多代码,但请记住,大约 50% 只是按钮的 html 样板,而不是“正确”解决方案的一部分:
// created 2017-10-04
function init()
let main = new Main();
function Main()
let factory = ;
console.log("entered main");
factory.boxEntity = document.querySelector('a-box');
factory.sound = factory.boxEntity.components.sound;
factory.volume = vol: factory.sound.data.volume;
factory.boxEntity.addEventListener('sound-loaded', ()=> console.log('sound loaded'));
factory.startBtn =document.querySelector('#btn-start');
factory.startBtn.onclick = ( function()
this.sound.stopSound();
let initVol = factory.sound.data.volume;
this.volume = vol: initVol; //need to do this every time
this.sound.pool.children[0].setVolume(initVol);
console.log(`onClick: volume=$this.sound.pool.children[0].getVolume()`);
this.sound.currentTime = 0.0;
if( this.tween)
this.tween.stop();
this.sound.playSound();
).bind(factory);
factory.hardStopBtn =document.querySelector('#btn-hard-stop');
factory.hardStopBtn.onclick = (function()
this.sound.stopSound();
).bind(factory);
factory.easyStopBtn =document.querySelector('#btn-easy-stop');
factory.easyStopBtn.onclick = (function()
let sound = factory.sound;
this.tween = new TWEEN.Tween(this.volume);
this.tween.to(
vol: 0.0
, document.querySelector('#fade-out-duration').value);
this.tween.onUpdate(function(obj)
console.log(`onUpdate: this.vol=$this.vol`);
sound.pool.children[0].setVolume(this.vol);
console.log(`onUpdate: pool.children[0].getVolume=$sound.pool.children[0].getVolume()`);
);
// Note: do *not* bind to parent context as tween passes it's info via 'this'
// and not just via callback parms.
// .bind(factory));
this.tween.onComplete(function()
sound.stopSound();
console.log(`tween is done`);
);
this.tween.start();
// animate is actually optional in this case. Tween will count down on it's
// own clock, but you might want to synchronize with your other updates. If this
// is an a-frame component, then you can just use the 'tick' method.
this.animate();
).bind(factory);
factory.animate = () =>
let id = requestAnimationFrame(factory.animate);
console.log(`now in animate`);
let result = TWEEN.update();
// cancelAnimationFrame is optional. You might want to invoke this to avoid
// the overhead of repeated animation calls. If you are putting this in an
// a-frame 'tick' callback, and there's other tick activity, you
// don't want to call this.
if(!result) cancelAnimationFrame(id);
return factory;
分析
以下是一些需要注意的相关事项。
混合 API
我正在调用一些原生 A 框架级别的调用:
sound.playSound()
sound.stopSound()
还有一个 html5 级别的调用:
this.sound.currentTime = 0.0;
但大部分“工作”都在三个.js 级别的调用中:
this.sound.pool.children[0].setVolume(initVol);
这确实让人有点困惑,但没有一个 api 是“完整的”,因此我不得不使用所有三个。特别是,我们必须在 A 帧包裹的关卡上做很多事情。我通过查看aframe source for the sound component
了解到了大部分内容声音池
Aframe 允许每个声音有多个线程,因此您可以在前一个声音完成之前触发相同的声音。这由声音组件上的poolSize
属性控制。我只处理第一个声音。我可能应该像这样循环池元素:
this.pool.children.forEach(function (sound)
..do stuff
);
但到目前为止,做第一个已经足够好了。时间会证明这是否可持续。
'this' 绑定
我选择使用工厂对象模式来实现所有功能,而不是将所有方法和变量都放在全局文档空间中。如果您在 Angular2 中实现或作为原生 A 框架组件,这模仿了您将拥有的环境。我提到这一点是因为我们现在将回调嵌套在嵌套在包装“main”函数中的函数中。因此请注意,“this”绑定可以发挥作用。我将大多数支持函数绑定到工厂对象,但不绑定补间回调,因为它们是在“this”上下文中传递信息,而不是通过参数传递。我不得不对回调使用闭包来访问包含类的实例变量。这只是标准的 javascript“回调地狱”的东西,但请记住,如果你不小心,它可能会让人感到困惑。
取消动画
如果你已经有一个刻度函数,用它来调用TWEEN.update()
。如果你只是淡出声音,那么让动画循环一直运行就有点过头了,所以在这个例子中,我动态地启动和停止动画循环。
tween 可以被链接。
Tweens 也可以以 jquery fluent API 样式链接。
结论
使用 tween.js 逐步消除声音绝对是正确的解决方案。它处理了很多开销和设计注意事项。与我之前使用的原生 html5 调用相比,它也感觉更快、更流畅、更健壮。然而,很明显,在应用程序级别上让它工作并非易事。在 Tween.js 中实现的淡出属性似乎应该是 A-frame 声音组件本身的一部分。但在那之前,也许有些人会发现我在这里提供的一些内容以某种形式有用。我自己目前只是在学习 html 音频,所以如果我让这看起来比实际上更难,我深表歉意。
【讨论】:
原来框架应用程序“a-blast”有一个“声音淡化”组件,看起来比我的解决方案更容易一些。我希望我早点知道这件事。那好吧。 github.com/aframevr/a-blast/blob/master/src/components/…以上是关于A-Frame:如何提供声音淡出以消除声音终止时的音频点击的主要内容,如果未能解决你的问题,请参考以下文章