Search code examples
audiohtml5-audio

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


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


Solution

  • 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.