howler.js icon indicating copy to clipboard operation
howler.js copied to clipboard

How to achieve smoother fades like setTargetAtTime() instead of linear fades?

Open taariqelliott opened this issue 5 months ago • 1 comments

Issue

The current fade() function creates linear volume transitions that can cause audible clicks and pops, especially when fading in/out quickly. I'm looking for a way to achieve smooth exponential fades similar to Web Audio API's setTargetAtTime().

Current Behavior

// Current implementation
sound.fade(sound.volume(), 1, 200);

Desired Behavior

// I've done this in the past to create smooth audio fades
volumeGainNode.gain.setTargetAtTime(0, audioCtx.currentTime, 0.04);

Code Example

const playSound = () => {
  if (!sound?.playing()) {
    sound?.fade(sound.volume(), 1, 200);
    setTimeout(() => {
      sound?.play();
    }, 100);
  }
};

const pauseSound = () => {
  if (sound?.playing()) {
    sound?.fade(sound.volume(), 0, 200);
    setTimeout(() => {
      sound?.pause();
    }, 100);
  }
};

Question

  1. Is there a way to configure Howler to use exponential fading curves instead of linear?
  2. Would it be possible to expose the underlying Web Audio gain node so we can use setTargetAtTime() directly?
  3. Are there any recommended workarounds for achieving smoother fades?

Reproducible Example

https://github.com/taariqelliott/ugubhu-v1/blob/main/src/routes/About.tsx

Reproduction Steps

Setup play/pause or mute/unmute functions as so:

  const playSound = () => {
    if (!sound?.playing()) {
      sound?.fade(sound.volume(), 1, 200);
      setTimeout(() => {
        sound?.play();
      }, 100);
    }
  };

  const pauseSound = () => {
    if (sound?.playing()) {
      sound?.fade(sound.volume(), 0, 200);
      setTimeout(() => {
        sound?.pause();
      }, 100);
    }
  };

Possible Solution

No response

Context

No response

Howler.js Version

2.2.4

Affected Browser(s)/Versiuon(s)

Electron[chromium]

taariqelliott avatar Aug 01 '25 06:08 taariqelliott

I was just looking at the fading code, and I noticed something interesting:

https://github.com/goldfire/howler.js/blob/a2a47933f1ffcee659e4939a65e075fa7f25706c/src/howler.core.js#L1332-L1340

If the Web Audio api is being used, the fade is done using linearRampToValueAtTime. But then it's also done via _startFadeInterval, which seems wrong. It seems like those two code paths should be exclusive (if/else).

Maybe this is contributing to the pops/clicks?

amoffat avatar Aug 21 '25 06:08 amoffat