Sound Popping/Crackling While Panning with p5.sound

I am trying to learn p5.sound by loading a sound, and panning it with a slider.

When I move the slider, especially if I move it too far too suddenly, the sound begins to crackle. Crackling while panning does not happen with DAWs on my computer, but only on browsers.

How can I eliminate the popping?

var sound;
var sliderPan
var button;

function preload() {
  sound = loadSound("formsoflife.mp3");
}
function setup() {
  createCanvas(400, 400);

  button = createButton("play");
  button.mousePressed(togglePlaying);

  sliderPan = createSlider(-1, 1, 0, 0.1);
}

function draw() {
  background(0);
  background (sound.currentTime(), 0, 255)
  sound.pan(sliderPan.value());
}

function togglePlaying() {
  if (!sound.isPlaying()) {
    sound.play();
    button.html("pause");
  } else if (sound.isPlaying()) {
    sound.pause();
    button.html("play");
  }
}


Hello and welcome, @umutreldem! :tada:

I’m guessing that the popping is less noticeable if you move the slider gently, right? If you move the slider fast back and forth, again and again, the popping is probably present all the time.

Think about what is actually happening in the physical world when you move that digital slider. You’re controlling electrons! Speakers produce sound by converting electricity to mechanical movement, after all.

In the extreme case, when panning from all left to all right very fast, distortions, in the form of the popping you’re describing, can be heard. The trick is to slowly ramp the value up and down instead of changing it right away.

Luckily, there’s built-in support for that. pan() has a second argument, timeFromNow, that lets you control the ramp time. Experiment with different values, and I’m sure you’ll find something that makes the popping less noticeable:

sound.pan(sliderPan.value(), 0.1);

I think the timeFromNow argument schedules the event n second into the future instead of creating a ramp. However, setting it to 0.1 does indeed work!

Thank you!

Are you sure about that? Taking a look at the source:

p5.Panner.prototype.pan = function (val, tFromNow) {
  var time = tFromNow || 0;
  var t = ac.currentTime + time;
  var v = (val + 1) / 2;
  var rightVal = Math.cos((v * Math.PI) / 2);
  var leftVal = Math.sin((v * Math.PI) / 2);
  this.left.gain.linearRampToValueAtTime(leftVal, t);
  this.right.gain.linearRampToValueAtTime(rightVal, t);
};

The implementation uses AudioParam.linearRampToValueAtTime():

The linearRampToValueAtTime() method of the AudioParam Interface schedules a gradual linear change in the value of the AudioParam . The change starts at the time specified for the previous event, follows a linear ramp to the new value given in the value parameter, and reaches the new value at the time given in the endTime parameter.

I am sure- I can confirm that if this is the code:

sound.pan(sliderPan.value(), 3);

Upon changing the slider nothing happens for three seconds, after which it suddenly “snaps” to the correct panning position.

Ah, interesting, it’s the “previous event” formulation in the documentation that I didn’t give enough attention. :blush: Thanks for pointing that out.

The following two lines should result in a slow pan during three seconds then (if that’s what you want).

sound.pan(sound.getPan());
sound.pan(sliderPan.value(), 3);

Hmm, using that code causes excessive popping during the 3-second pan… Might need to write a function that splits the 3 seconds into increments of 0.1 seconds.

I suspect that’s caused by the panning being done in draw(). draw() is called 60 times per second on most machines. Just as a test, what if you do the pan everytime the slider value is changed instead:

function setup() {
  // [...]

  sliderPan = createSlider(-1, 1, 0, 0.1);
  sliderPan.changed(() => {
    sound.pan(sound.getPan());
    sound.pan(sliderPan.value(), 3);
  });
}

https://p5js.org/reference/#/p5/changed

It does kind-of work! The only problem is that if you change slider before current the 3-second ramp is completed, it starts immediately from the end of the ramp.

Glad to hear that. That makes sense, the line sound.pan(sound.getPan()) causes this.

I’m afraid what you want is not directly achievable via p5.js-sound API:s. You either have to look one level below, working directly with the Web Audio API:s or create a workaround.

Maybe by splitting up the increments, as you suggested earlier, or by disabling the slider while panning is in progress. (The user has to wait until it is finished.)