Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webaudio panner crackling sound when position is changed

I'm working on a 3D web app where I want to use positional 3D audio.

I started noticing that a crackling noise appears on the output as the sound source changes its position. Initially I thought it could be a programming issue or a library issue (I was using howler.js).

I made a very basic example based on plain JS and Webaudio API which is shown here

let params={        
        "xPosition":0,
        "zPosition":-1
    };

    let gui=new dat.GUI( { autoPlace: true, width: 500 });       

    const AudioContext = window.AudioContext || window.webkitAudioContext;
    
    let audioCtx;
    let panner;
    let listener;
    let source;
    let osc;
  

    function initWebAudio(){
    
        audioCtx = new AudioContext();
        panner = audioCtx.createPanner();
        listener = audioCtx.listener;

        osc = audioCtx.createOscillator();
        osc.frequency.value = 70;
        osc.connect(panner);
        osc.start(0);


        panner.connect(audioCtx.destination);

        panner.panningModel = 'HRTF';
        panner.distanceModel = 'linear';
        
        panner.maxDistance = 60;
        panner.refDistance = 1;
        panner.rolloffFactor = 1;
        panner.coneInnerAngle = 360;
        panner.coneOuterAngle = 360;
        panner.coneOuterGain = 0;
        
                panner.positionX.setValueAtTime(0,audioCtx.currentTime);
        panner.positionY.setValueAtTime(1,audioCtx.currentTime);
                panner.positionZ.setValueAtTime(1,audioCtx.currentTime);
        
        if(panner.orientationX) {
            panner.orientationX.value = 1;
            panner.orientationY.value = 0;
            panner.orientationZ.value = 0;
        } else {
            panner.setOrientation(1,0,0);
        }

        if(listener.forwardX) {
            listener.forwardX.value = 0;
            listener.forwardY.value = 0;
            listener.forwardZ.value = -1;

            listener.upX.value = 0;
            listener.upY.value = 1;
            listener.upZ.value = 0;
        } else {
            listener.setOrientation(0,0,-1,0,1,0);
        }


        if(listener.positionX) {
            listener.positionX.value = 0;
            listener.positionY.value = 0;
            listener.positionZ.value = 0;
        } else {
            listener.setPosition(0,0,0);
        }

    }

    function positionPanner() {
        
        if(panner.positionX) {
        
            panner.positionX.setValueAtTime(params.xPosition, audioCtx.currentTime);          
            
        } else {
        
            panner.setPosition(params.xPosition,0,params.zPosition);
        }       

    }
    

    function tick(){  
    
        positionPanner();  
        
    }
        
    function onClickStart(){

        initWebAudio();    
        
        gui.add(osc.frequency,"value",50,220).name("frequency");
        
        setInterval(tick,50);

    }
    
    function buildMenu(){

        gui.add(params,"xPosition",-3,3).step(0.001);
        gui.add(window,"onClickStart").name("start");
        
    }

    
    buildMenu();  
    

https://jsfiddle.net/fedeM75/t9vpm8so/23/

Press start, then as you change the xPosition slider a crackling sound appears. It is specially noticeable using headphones.

I searched on google and some people say that it has to do with the rate of change of the position. But I tried with different value ranges and it still happens with small changes.

By the way if the condition to use the panner is to have a very slow rate of change in the position it does not seem to be useful in real world cases.

I use a timer to update the position but the same happens using requestAnimationFrame()

Does anyone has a clue on why this happens and how to solve it?

like image 769
Federico Marino Avatar asked Nov 15 '22 20:11

Federico Marino


1 Answers

I had this crackling audio with a simple GainNode. It could be a bug indeed since a simple implementation in the ScriptProcessor does work. Or it's the fact that I/we just don't get how the timing with audioCtx.currentTime should be handled?

For me the solution was to ditch the gain node and use a ScriptProcessor (deprecated: you could/should use an AudioWorkletNode) with my own gain applied to the audio signal directly:

let myGain = 1.0; // change this as you like without crackling noises

let whiteNoise = audioCtx.createScriptProcessor(4096, 0, 1);
whiteNoise.onaudioprocess = function(e) {
    let output = e.outputBuffer.getChannelData(0);
    for (let i = 0; i < output.length; i++) {
        output[i] = (Math.random() * 2 - 1) * myGain;
    }
}

This won't fix your problem directly since you have this problem with the 3D panner, but perhaps you can find sourcecode/examples for such a panner implementation and port it to an AudioWorkletNode? Hope this helps.

like image 174
Almer Avatar answered Dec 25 '22 17:12

Almer