How can I avoid this 'clicking' sound when

2019-02-05 02:44发布

问题:

I really hope this question stays a programming question and do not end up an Sound Mechanics question... Here goes...

I am doing some experiments in order to figure out how the Web Audio API works. What I am trying to do is a simple "Hang up phone" sound playing in a loop. The problem is that when the sound ends, you can hear a quite annoying 'clicking' sound. I cannot explain it better, but you can hear it if you test the code.

Is there some way I could avoid this? Some filter I could apply or anything?

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

回答1:

This is an audio issue, not a programming problem. The click you hear occurs when a waveform is stopped/cut in the middle of a wave, rather than at a zero-crossing.

The best simple solution from a audio paradigm is to very quickly fade-out, instead of just stopping playback.

A slightly more complex solution is to find the next zero-crossing and stop playback at precisely that point.



回答2:

Looks like the Web Audio API gives the developer an easy way of stopping a sound source from playing without abruptly stopping the waveform and avoid any noise and sound artifacts.

  1. Create your sound source (in my example an oscillator)
  2. Create a gain node and connect it with the sound source.
  3. Start the sound source and set the gain value to 0. That way, you won't listen to the sound even if it's technically playing
  4. Set the gain value to 1 when you want the source to play and to 0 when it should not play. The gain node will handle the rest, and no clicking will be heard

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



回答3:

There's a brief explanation of why we hear the clicking sound (it's a human ear thing) and good examples of how to get around that using the Web audio API here: http://alemangui.github.io/blog//2015/12/26/ramp-to-value.html

The main takeaway from the article is that the exponential methods to remove the click work better; exponentialRampToValueAtTime and setTargetAtTime.

Using setTargetAtTime to remove the click

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

Using exponentialRampToValueAtTime to remove the click

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

Both of these worked for me in my use case, with exponentialRampToValueAtTime working slightly better. I could still hear a faint click using setTargetAtTime.