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:如何提供声音淡出以消除声音终止时的音频点击的主要内容,如果未能解决你的问题,请参考以下文章

如何在目标 c 中增加声音的音量并消除背景噪音

初学者 python 编程帮助交叉淡入淡出声音

A-Frame 中的点击事件不会停止声音

EDIUS中声音的淡入淡出怎样调节?

java声音淡出

淡入/淡出声音