当我停止播放声音时,如何避免这种'clicking'声音?



我真的希望这个问题仍然是一个编程问题,而不是一个声音力学问题。。。给。。。

我正在做一些实验,以便弄清楚Web Audio API是如何工作的。我想做的是循环播放一个简单的"挂断电话"的声音。问题是,当声音结束时,你会听到一种很烦人的"咔嗒"声。我无法更好地解释它,但如果你测试代码,你可以听到它。

有什么办法可以避免这种情况吗?我可以应用一些过滤器或其他什么?

var audioContext = new (AudioContext || webkitAudioContext)();
    
    var frequencyOffset = 0
    function boop(){
      // Our sound source is a simple triangle oscillator
      var oscillator = audioContext.createOscillator(); // Create sound source  
      oscillator.type = 'triangle';
      
      // Adding a gain node just to lower the volume a bit and to make the
      // sound less ear-piercing
      var gain = audioContext.createGain();
      oscillator.connect(gain);
      gain.connect(audioContext.destination);
      
      gain.gain.value = 0.1;
      // Just for fun let the frequency increase on each itteration
      oscillator.frequency.value = 200 + frequencyOffset;
      oscillator.start(0);
      
      // The sound should last for 250ms
      setTimeout(function(){
        oscillator.disconnect(); 
        oscillator.stop();
        gain.disconnect();
      }, 250);
      frequencyOffset += 1;
    }
    setInterval(boop, 500);

这是音频问题,而不是编程问题。当波形在波的中间停止/切断时,而不是在过零点时,会发生您听到的咔嗒声。

音频模式中最好的简单解决方案是快速淡出,而不是停止播放。

稍微复杂一点的解决方案是找到下一个过零点,并在该点停止播放。

这里有一个关于我们为什么会听到点击声(这是人耳的事情)的简短解释,以及如何使用Web音频API绕过点击声的好例子:http://alemangui.github.io/blog//2015/12/26/ramp-to-value.html

这篇文章的主要收获是,消除点击的指数方法效果更好指数RampToValueAtTime设置目标AtTime

使用setTargetAtTime删除点击

var context = new AudioContext();
var oscillator = context.createOscillator();
var gainNode = context.createGain();
oscillator.connect(gainNode);
gainNode.connect(context.destination)
oscillator.start();
stopButton.addEventListener('click', function() {
    gainNode.gain.setTargetAtTime(0, context.currentTime, 0.015);
});

使用exponentialRampToValueAtTime删除点击

var context = new AudioContext();
var oscillator = context.createOscillator();
var gainNode = context.createGain();
oscillator.connect(gainNode);
gainNode.connect(context.destination)
oscillator.start();
stopButton.addEventListener('click', function() {
    // Important! Setting a scheduled parameter value
    gainNode.gain.setValueAtTime(gainNode.gain.value, context.currentTime); 
    gainNode.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.03);
});

这两种方法在我的用例中都适用,exponentialRampToValueAtTime的效果稍好。使用setTargetAtTime,我仍然可以听到微弱的点击声。

看起来Web Audio API为开发人员提供了一种简单的方法,可以在不突然停止波形的情况下停止播放声源,并避免任何噪声和声音伪影。

  1. 创建你的声源(在我的例子中是一个振荡器)
  2. 创建一个增益节点并将其与声源连接
  3. 启动声源并将增益值设置为0。这样,即使从技术上讲是在播放,你也听不到声音
  4. 当您希望播放源时,将增益值设置为1,当不应该播放时,将其设置为0。增益节点将处理其余部分,不会听到任何点击声

var audioContext = new(AudioContext || webkitAudioContext)();
var frequencyOffset = 0
  // Our sound source is a simple triangle oscillator
var oscillator = audioContext.createOscillator(); // Create sound source  
oscillator.type = 'triangle';
// Adding a gain node just to lower the volume a bit and to make the
// sound less ear-piercing. It will also allow us to mute and replay
// our sound on demand
var gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
gainNode.gain.value = 0;
oscillator.frequency.value = 200;
oscillator.start(0);
function boop() {
  gainNode.gain.value = 0.1;
  // The sound should last for 250ms
  setTimeout(function() {
    gainNode.gain.value = 0;
  }, 250);
  oscillator.frequency.value++;
}
setInterval(boop, 500);

timmcliu的回答很好,但有两件事进一步降低了我的点击声:

  1. 我使用linearRampToValueAtTime。我想这会使变化更加平缓,从而软化点击
  2. 我已经将先决条件noop setValueAtTime也安排在未来。我想这会使更改从下一个渲染量子而不是当前量子开始应用。我认为如果没有这一点,渐变开始得太突然了,因为浏览器无法编辑已经预缓冲的渲染量(128个样本,IIUC)

基本上是这样的:

stopButton.addEventListener('click', function() {
    let t = context.currentTime;
    gainNode.gain.setValueAtTime(gainNode.gain.value, t + 0.02);
    gainNode.gain.linearRampToValueAtTime(0.0001, t + 0.04);
});

最新更新