网络音频api,优雅地停止声音 [英] Web audio api, stop sound gracefully

查看:174
本文介绍了网络音频api,优雅地停止声音的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

网络音频api提供方法 .stop()来停止声音。
我希望我的声音在停止前减小音量。为此,我使用了增益节点。然而,我面临着一些奇怪的问题,有些声音根本无法发挥,我无法弄清楚原因。

The web audio api furnish the method .stop() to stop a sound. I want my sound to decrease in volume before stopping. To do so I used a gain node. However I'm facing weird issues with this where some sounds just don't play and I can't figure out why.

这是我的一个愚蠢的版本执行:

Here is a dumbed down version of what I do:

https://jsfiddle.net/ 01p1t09n / 1 /

如果你用删除该行,则会听到setTimeout()每个声音播放。当setTimeout不是每个声音播放。让我感到困惑的是我使用 push shift 相应地找到了正确的声音源,但似乎喜欢这是另一个停止播放。我能看到这种情况的唯一方法是,如果 AudioContext.decodeAudioData 不同步。只需尝试jsfiddle就可以更好地理解你的耳机了。

You'll hear that if you remove the line with setTimeout() that every sound plays. When setTimeout is there not every sound plays. What really confuses me is that I use push and shift accordingly to find the correct source of the sound, however it seems like it's another that stop playing. The only way I can see this happening is if AudioContext.decodeAudioData isn't synchronous. Just try the jsfiddle to have a better understanding and put your headset on obviously.

以下是jsfiddle的代码:

Here is the code of the jsfiddle:

  let url = "https://raw.githubusercontent.com/gleitz/midi-js-soundfonts/gh-pages/MusyngKite/acoustic_guitar_steel-mp3/A4.mp3";
  let soundContainer = {};
  let notesMap = {"A4": [] };
  let _AudioContext_ = AudioContext || webkitAudioContext;
  let audioContext = new _AudioContext_();

  var oReq = new XMLHttpRequest();
  oReq.open("GET", url, true);
  oReq.responseType = "arraybuffer";
  oReq.onload = function (oEvent) {
    var arrayBuffer = oReq.response; 
    makeLoop(arrayBuffer);
  };
  oReq.send(null);

  function makeLoop(arrayBuffer){
     soundContainer["A4"] = arrayBuffer;
     let currentTime = audioContext.currentTime;
     for(let i = 0; i < 10; i++){
        //playing at same intervals
            play("A4", currentTime + i * 0.5);
        setTimeout( () => stop("A4"), 500 + i * 500); //remove this line you will hear all the sounds.
     }
  }

  function play(notePlayed, start) {    

      audioContext.decodeAudioData(soundContainer[notePlayed], (buffer) => {
      let source; 
      let gainNode; 
        source = audioContext.createBufferSource(); 
        gainNode = audioContext.createGain();
        // pushing notes in note map
        notesMap[notePlayed].push({ source, gainNode });
        source.buffer = buffer;                   
        source.connect(gainNode);
        gainNode.connect(audioContext.destination);
        gainNode.gain.value = 1;
        source.start(start);
       });
    }

      function stop(notePlayed){    
        let note = notesMap[notePlayed].shift();

        note.source.stop();
     }









这只是为了解释为什么我这样做,你可以跳过它,它只是解释为什么我不使用stop()

我正在做这一切的原因是因为我想优雅地停止声音,所以如果有可能不使用setTimeout这样做,我很乐意接受它。

The reason I'm doing all this is because I want to stop the sound gracefully, so if there is a possibility to do so without using setTimeout I'd gladly take it.

基本上我的顶部有一张包含我的声音的地图(如A1,A#1,B1,......等注释)。

Basically I have a map at the top containing my sounds (notes like A1, A#1, B1,...).

soundMap = {"A": [], "lot": [], "of": [], "sounds": []};

play() fct我在哪里播放声音后填充数组:

and a play() fct where I populate the arrays once I play the sounds:

  play(sound) { 
    // sound is just { soundName, velocity, start}   
    let source; 
    let gainNode; 
    // sound container is just a map from soundname to the sound data.
    this.audioContext.decodeAudioData(this.soundContainer[sound.soundName], (buffer) => {
      source = this.audioContext.createBufferSource(); 
      gainNode = this.audioContext.createGain();
      gainNode.gain.value = sound.velocity;
      // pushing sound in sound map
      this.soundMap[sound.soundName].push({ source, gainNode });
      source.buffer = buffer;                   
      source.connect(gainNode);
      gainNode.connect(this.audioContext.destination);
      source.start(sound.start);
     });
  }

现在停止声音的部分:

  stop(sound){   
    //remember above, soundMap is a map from "soundName" to {gain, source} 
    let dasound = this.soundMap[sound.soundName].shift();
    let gain = dasound.gainNode.gain.value - 0.1;

    // we lower the gain via incremental values to not have the sound stop abruptly
    let i = 0;
    for(; gain > 0; i++, gain -= 0.1){ // watchout funky syntax
      ((gain, i) => {
        setTimeout(() => dasound.gainNode.gain.value = gain, 50 * i );
      })(gain, i)
    }
    // we stop the source after the gain is set at 0. stop is in sec
    setTimeout(() => note.source.stop(), i * 50);
  }


推荐答案

Aaah,是的,是的,是!我终于找到了很多东西,最终还是费心去阅读doc中的一切(对角线)。让我告诉你这个api是粗糙的钻石。无论如何,他们实际上有我想要的音频参数

Aaah, yes, yes, yes! I finally found a lot of things by eventually bothering to read "everything" in the doc (diagonally). And let me tell you this api is a diamond in the rough. Anyway, they actually have what I wanted with Audio param :


AudioParam接口表示与音频相关的参数,通常是AudioNode的参数(例如GainNode.gain)。
AudioParam可以设置为特定值或值的变化,
可以安排在特定时间发生并遵循特定的
模式。

The AudioParam interface represents an audio-related parameter, usually a parameter of an AudioNode (such as GainNode.gain). An AudioParam can be set to a specific value or a change in value, and can be scheduled to happen at a specific time and following a specific pattern.

它有一个函数 linearRampToValueAtTime()

他们甚至有一个我问过的例子!

And they even have an example with what I asked !

// create audio context
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioCtx = new AudioContext();

// set basic variables for example
var myAudio = document.querySelector('audio');
var pre = document.querySelector('pre');
var myScript = document.querySelector('script');

pre.innerHTML = myScript.innerHTML;

var linearRampPlus = document.querySelector('.linear-ramp-plus');
var linearRampMinus = document.querySelector('.linear-ramp-minus');

// Create a MediaElementAudioSourceNode
// Feed the HTMLMediaElement into it
var source = audioCtx.createMediaElementSource(myAudio);

// Create a gain node and set it's gain value to 0.5
var gainNode = audioCtx.createGain();

// connect the AudioBufferSourceNode to the gainNode
// and the gainNode to the destination
gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
source.connect(gainNode);
gainNode.connect(audioCtx.destination);

// set buttons to do something onclick
linearRampPlus.onclick = function() {
  gainNode.gain.linearRampToValueAtTime(1.0, audioCtx.currentTime + 2);
}

linearRampMinus.onclick = function() {
  gainNode.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 2);
}

这里的工作示例

他们也有不同类型的时间,比如指数而不是线性斜坡我认为适合这种情况更多。

They also have different type of timings, like exponential instead of linear ramp which I guess would fit this scenario more.

这篇关于网络音频api,优雅地停止声音的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆