Skip to content

Conversation

@tatsmaki
Copy link
Contributor

@tatsmaki tatsmaki commented Dec 13, 2025

Related issue: #32482

Description

Added audio volume fading functionality using setTargetAtTime and cancelScheduledValues.

It is also possible to use linearRampToValueAtTime or exponentialRampToValueAtTime for precise transition control, however there is limited Chrome Android / WebView support.

TODO:

  • Add tests ?
  • Add example ?

@github-actions
Copy link

github-actions bot commented Dec 13, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 355.17
84.44
355.17
84.44
+0 B
+0 B
WebGPU 618.71
171.79
618.71
171.79
+0 B
+0 B
WebGPU Nodes 617.31
171.54
617.31
171.54
+0 B
+0 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 487.34
119.31
487.34
119.31
+0 B
+0 B
WebGPU 689.34
187.21
689.34
187.21
+0 B
+0 B
WebGPU Nodes 639.18
174.41
639.18
174.41
+0 B
+0 B

@tatsmaki tatsmaki marked this pull request as ready for review December 15, 2025 21:49
* @param {number} [volumeThen=1] - The volume at the end of the fade.
* @return {Audio} A reference to this instance.
*/
fadeIn( duration, volumeNow = 0, volumeThen = 1 ) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've quickly checked Unreal and they also have fadein/fadeout in their Blueprint API:

https://dev.epicgames.com/documentation/en-us/unreal-engine/BlueprintAPI/Audio/Components/Audio/FadeIn
https://dev.epicgames.com/documentation/en-us/unreal-engine/BlueprintAPI/Audio/Components/Audio/FadeOut

When checking our own methods, I think fadeIn() and fadeOut() should have an optional delay parameter similar to play() and stop(). It's in seconds and you add it currentTime when scheduling the values. New singnature:

fadeIn( duration, volumeNow = 0, volumeThen = 1, delay = 0 ) {

Also, do we need volumeNow? Can't we just use the current volume like in fadeOut(). It seems the Unreal component also just allows to define the target volume.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think we can set the volumeNow beforehand

audio.setVolume(0.1);
audio.fadeIn(3, 0.5);

although there might be some issues because setVolume does not set the volume immediately.
it is using the same setTargetAtTime API with a small 0.01 timeConstant.
so it could be setVolume will be canceled by fadeIn

Copy link
Collaborator

@Mugen87 Mugen87 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we save the value passed to setVolume() is a private variable an use it in the fade methods? In this way, the value set via setVolume() isn't lost when fading in an audio like in https://github.com/mrdoob/three.js/pull/32549/files#r2622754999. The implementation would use:

return this._scheduleFading( duration, this._volume, volumeThen );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check if it works fine or not with the above example
it was just a hypothesis

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this old issue #12510

do you remember why setTargetAtTime was used and not setValueAtTime ?

Screenshot 2025-12-17 at 10 22 39
  1. if possible I would replace it with setValueAtTime (instant volume update)
  2. your suggestion with this._volume
  3. this is evil but currentTime + 0.01 * 3 will make sure setVolume smoothing is complete

Copy link
Contributor Author

@tatsmaki tatsmaki Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we care about cases when people access audio.gain.gain.value directly?
because in this scenario this._volume will not be set

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not ideal that gain is public but in general we expect developers work with setVolume().

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

audio.setVolume(0.1);
audio.fadeIn(3, 0.5);

I've tested the code today an realized the usage of setTargetAtTime() in setVolume() does not work with the new fading methods. In the above code example, when fadeIn() is executed, the current volume is not set to 0.1 yet which means the interpolation to the target value won't work. After some testing this can only be fixed by using setValueAtTime() in setVolume(). Or by using your original implementation. However, I'm not a fan of setting the current volume in the fade methods since we already have setVolume() for that.

So I think before we can make this change, we have to migrate from setTargetAtTime() to setValueAtTime(). But that requires good testing and some research in older issues since setTargetAtTime() was chosen on purpose, see https://github.com/mrdoob/three.js/pull/32549/files#r2631137839.

Copy link
Collaborator

@Mugen87 Mugen87 Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some more reading I'm not feeling comfortable to make the change from setTargetAtTime() to setValueAtTime(). As explained in WebAudio/web-audio-api#76 (comment), setTargetAtTime() provides a De-zippering behavior:

De-zippering is the process of smoothly approaching a target value instead of jumping to it directly. The intention is to prevent audio "glitches" which might occur if the value is changed suddenly.

setValueAtTime() does not:

If a developer explicitly sets values (using setValueAtTime for example) then de-zippering is not performed.

So if we use setValueAtTime(), we risk to introduce (platform specific) audio regressions e.g. when changing the volume. Bugs like crackling sounds were reported in the past (I actually heard them myself) and we don't experience them since we use setTargetAtTime().

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reconsideration it's probably better to close the PR and let fadein/fadeouts be implemented on application level. _scheduleFading() depends on setValueAtTime() and the consequences of using this method in audio animations are unclear.

@Mugen87 Mugen87 closed this Jan 6, 2026
@Mugen87 Mugen87 added this to the r183 milestone Jan 6, 2026
@Mugen87 Mugen87 mentioned this pull request Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants