A-Frame:如何提供声音淡出以消除音频点击声音终止

时间:2017-10-06 10:07:49

标签: aframe

A-frame通过其<sound> component提供易于使用且功能强大的音频功能。

在为我的游戏(正在进行)中使用各种声音选项(例如原生html5)之后,我得出的结论是A帧声音是最佳选择,因为它会自动提供空间化声音(例如,随着头部旋转而变化) ,以及靠近声源的强度变化 - 增加VR存在的东西,以及定义简单html标签的成本。

不幸的是,A帧不提供淡出功能,可以在停止时逐渐减小声音,从而可以在某些波形上产生明显的声音和恼人的点击,尤其是。声音长度可变,波形本身不呈锥形(例如太空船的推力)。这是一个带有计算机音频的well known problem

我能够找到一些html5 audio solutions和一个非常好的three.js音频three.js audio solution,但我找不到特定的A帧。

在A帧中逐渐减少声音以减少/消除此点击的最佳方法是什么?

1 个答案:

答案 0 :(得分:0)

简介

A帧sound音频包装three.js positional audio API,后者又包含原生html 5音频。大多数解决方案都是针对纯html5或纯粹的three.js量身定制的。由于A帧是两个api的混合体,所提供的解决方案都不适合A帧。

在提出两件错误的开始之后,我发现了tween.js,它不仅内置于A-frame(甚至不必下载库),而且还是一个用于了解其他形式的计算机动画的有用API。我提供了这里的主要解决方案以及a plunker,希望其他人能找到有用的东西。

请注意,您不需要为子弹射击等短暂爆发声音执行此操作。这些声音具有固定的寿命,因此大概创建波形的人确保将它们逐渐变细。另外,我只处理淡出,不淡化因为我需要的声音只有淡出的问题。一般的解决方案也包括fadein。

解决方案

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)代码提供了三个按钮:一个用于启动声音,一个用于&#34; hard&#34;使用A帧默认设置停止,第三个设置为&#34; easy&#34;使用补间停止它以将其逐渐减小到零。第四个输入允许您改变锥度时间。虽然它可能看起来像相当多的代码,但请记住,50%只是按钮的html样板,并且不是解决方案的一部分&#34;正确&#34;:

// 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&#39;

我正在调用一些原生的A帧级别调用:

sound.playSound()
sound.stopSound()

和一个html5级别的电话:

this.sound.currentTime = 0.0;

但大多数&#34;工作&#34;在三个级别的电话中:

this.sound.pool.children[0].setVolume(initVol);

这确实让它有点混乱,但没有单一的api&#34;完成&#34;因此我不得不使用这三个。特别是,我们必须在由A-frame包裹的级别上做很多事情。我通过查看aframe source for the sound component

了解了大部分内容

声音池

Aframe允许每个声音使用多个线程,这样您就可以在前一个声音完成之前触发相同的声音。这由声音组件上的poolSize属性控制。我只处理第一个声音。我应该像这样循环池元素:

this.pool.children.forEach(function (sound) {
  ..do stuff
  }
});

但到目前为止,做第一个效果还不错。时间将证明这是否可持续。

&#39;这&#39;结合

我选择使用工厂对象模式实现所有功能,而不是将所有方法和变量放在全局文档空间中。如果您在Angular2中实现或作为本机A帧组件实现,那么这将模仿您将拥有的环境。我提到这个是因为我们现在有嵌套在嵌套在包装内的函数的回调&#34; main&#34;功能。因此要注意&#34;这个&#34;绑定可以发挥作用。我将大多数支持函数绑定到工厂对象,但是绑定补间回调,因为它们在&#34; this&#34;中传递信息。上下文,而不是通过parms传递。我不得不求助于闭包来获取对包含类的实例变量的访问。这只是标准的javascript&#34;回调地狱&#34;事情,但请记住,如果你不小心,它可能会让人感到困惑。

取消动画

如果您已经有一个勾选功能,请使用它来呼叫TWEEN.update()。如果你只是淡出声音,那么让动画循环一直运行就太过分了,所以在这个例子中我动态地启动和停止动画循环。

补间可以链接。

Tweens也可以用jquery流畅的API样式链接。

结论

使用tween.js逐步淘汰声音绝对是一种正确的解决方案。它需要考虑很多开销和设计考虑因素。它比我之前使用的本机html5调用感觉更快,更流畅,更健壮。但是,很明显,在应用程序级别上工作并不是一件容易的事。在Tween.js中实现的淡出属性似乎应该是A帧声音组件本身的一部分。但在那之前,也许有些人会发现我在这里提供的某些东西在某种形式上是有用的。我现在只是在学习html音频,所以如果我觉得这看起来比实际上更难,那就道歉了。