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

How can I avoid this 'clicking' sound when I stop playing a sound?

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

我正在做一些实验以了解网络音频 API 的工作原理。我想要做的是循环播放一个简单的 "Hang up phone" 声音。问题是,当声音结束时,您会听到很烦人的 'clicking' 声音。我无法更好地解释它,但是如果您测试代码,您可以听到它。

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

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);

这是音频问题,不是编程问题。当波形 stopped/cut 位于波的中间而不是过零时,您会听到咔哒声。

音频范例中最好的简单解决方案是非常快速地淡出,而不是仅仅停止播放。

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

看起来 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);

这里简要解释了为什么我们会听到咔嗒声(这是人耳的声音),以及如何使用网络音频 API 解决这个问题的好例子: http://alemangui.github.io/blog//2015/12/26/ramp-to-value.html

这篇文章的主要收获是指数法去除点击的效果更好; exponentialRampToValueAtTimesetTargetAtTime.

使用 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.

听到微弱的咔嗒声