【问题标题】:A-Frame: How to provide sound fadeout to eliminate audio click upon sound terminationA-Frame:如何提供声音淡出以消除声音终止时的音频点击
【发布时间】: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 帧中逐渐减小声音以减少/消除这种咔嗒声的最佳方法是什么?

【问题讨论】:

    标签: aframe


    【解决方案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 音频,所以如果我让这看起来比实际上更难,我深表歉意。

    【讨论】:

    猜你喜欢
    • 2019-05-21
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-03
    • 2021-12-11
    • 2019-09-29
    • 1970-01-01
    相关资源
    最近更新 更多