Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audio clips when changing volume of audio bus #32882

Open
Tracked by #76797
ARPasescu opened this issue Oct 16, 2019 · 13 comments
Open
Tracked by #76797

Audio clips when changing volume of audio bus #32882

ARPasescu opened this issue Oct 16, 2019 · 13 comments

Comments

@ARPasescu
Copy link

ARPasescu commented Oct 16, 2019

Godot version: 3.1.1 stable
OS: Windows 10 Pro version 1903 build 18362.418
Device: ASUS ROG Zephyrus S GX502GW (using both integrated laptop speaker and OnePlus USB-C bullet earbuds)

Issue description:
Changing the volume of an audio bus using AudioServer.set_bus_volume_db() or through the visual interface (Audio bottom tab) produces very audible clipping noises. Comparatively, changing the volume_db property on an AudioStreamPlayer produces virtually zero clipping.

Steps to reproduce:
Create a scene with an ASP and set it to the master channel. Load some music file on it, set Playing to true and play with the volume property knob. Notice virtually no sound clipping. Now open the audio tab on the bottom and play the same sound file. Try changing the volume of the master bus with the vertical bar. Sound will clip a lot as you change the volume.

Bugsquad edit (keywords for easier searching): crack, cracking

@ARPasescu ARPasescu changed the title Audio clips when using AudioStream.set_bus_volume_db() Audio clips when changing volume of audio bus Oct 16, 2019
@Zylann
Copy link
Contributor

Zylann commented Oct 20, 2019

Volume changes need to apply some short ramping to the final playback, otherwise the amplified waveforms will look like this:
image

Same happens in many smaller steps if you ramp the volume from a node that runs in _process.

@Calinou
Copy link
Member

Calinou commented Oct 20, 2019

@Zylann Which duration would you recommend for the ramping? Also, does a specific easing method need to be used, or is linear interpolation fine?

@Zylann
Copy link
Contributor

Zylann commented Oct 20, 2019

@Calinou I don't know, maybe 10, 20 samples? Need to be tested. This kind of problem occurred in the past (I think it was for looping?) and that's how it got fixed.

@lawnjelly
Copy link
Member

@Calinou Even better might be to allow the user to specify the fade time, and have a default. Linear interpolation will probably be fine.

e.g. Store the current volume, the target volume, and the end time of the fade. That way if another fade is requested before the first is complete you can just replace the values and it should work fine.

I think with short fades the danger is that the fade itself can introduce extra artefact frequencies into the sound (with zero fade being worst case as it is now). Low frequencies may be more susceptible. If you think about a 50hz sin wave, fading it out from a peak over 1/8 of this period (1/50 * 1/8) will lead to a waveform at approx 100hz over the fade, giving a slight audible glitch.

I suppose in theory we could calculate the minimum fade needed (this is just guesswork lol 😄 ):

If we consider that the minimum frequency we could hear is 10 hz (giving it a bit of leeway), then in a worst case scenario we are fading a constant 1.0 value to 0.0, we would want any artefact frequencies created to be below 10hz. For a sine wave fade, this would be 1/10 second * 1/4 (peak to 0) = 1/40 second.

With a linear fade this would produce a triangle wave rather than a sine, so you would get a bunch of higher artefact frequencies which is not ideal, but still that should give us some idea of the minimum fade. In practice I would think 1/10th of a second would be about right.

The reason I suggest also having a user specified fade time is that if the user does want to try and do a fade over say 10 seconds manually, and you have a fixed short fade time, this will come out as a series of jumps in volume rather than a smooth fade.

Of course all this is far more important for note-offs in music, and I don't know the use cases for bus volume changes - probably things like user changing volume, and moving between areas with different sound effect levels. So if you did go for a fixed fade time there would be an argument for using a larger value like a second.

@Calinou
Copy link
Member

Calinou commented Oct 21, 2019

@lawnjelly I'd say slow fades are AnimationPlayer/Tween territory. The built-in fade should only be there to prevent audible clicks, not for effects.

@lawnjelly
Copy link
Member

That is the thing about audio though, you can't use AnimationPlayer/Tween, because it is not frame or tick based, it runs on a separate thread and needs to be interpolated continuously. If you attempt to use a Godot node to do a slow fade, you'll get audio anomalies. It's a bit of a special case.

@fadexmusic
Copy link

I also get this issue when tweening the volume of an audio stream player. Is there any way to do this better ?

@mortarroad
Copy link
Contributor

mortarroad commented Apr 12, 2021

Note that AudioStreamPlayer already interpolates the volume, across one buffer size:

//multiply volume interpolating to avoid clicks if this changes
float target_volume = p_fadeout ? -80.0 : volume_db;
float vol = Math::db2linear(mix_volume_db);
float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size);
for (int i = 0; i < buffer_size; i++) {
buffer[i] *= vol;
vol += vol_inc;
}

The same could be done for the buses.

Apparently the buffer is 256 samples long, i.e. around 5 ms, which is a bit short.

INTERNAL_BUFFER_LEN = 256,

Searching around the internet, I read that values around 20 ms and 50 ms should be fine for a linear ramp.

@mischiefaaron
Copy link

mischiefaaron commented May 13, 2021

Hello. I am the troublemaker that created the now closed duplicate.
Just wanted to share my hacky workaround in case it helps someone while this isn't working.

What you need to do is adjust the player/stream's volume directly which does not have this issue.
In this case you would use a value that normally adjusts the busses volume directly. When a user accesses the volume changing options you will instead adjust the stream's volume through the music/sound player's node. The bus volume will in turn be set to full (0db/100 linear in my case). Then when the user exits your options you want them to swap back around with the bus volume being set to the saved values and the player volume being set to full.

@Calinou
Copy link
Member

Calinou commented Nov 16, 2021

Can anyone reproduce this on the master branch since #51296 was merged?

@ideaplexer
Copy link

ideaplexer commented Nov 26, 2021

A reliable workaround I have found for this is to use the delay effect on the appropriate audio bus, then enable one of the taps, set its level_db to -60 (silence) and tween the dry parameter with values between 0 and 1 whenever you want to change the volume. For the sake of completeness and for future readers who come across this page, I will explain what the effect does in detail.

The delay effect ( AudioEffectDelay ) repeats the audio signal generated by the bus it is currently enabled in, but after a certain amount of delay. The effect allows you to repeat the signal two times with different parameters (called taps) and 'feedback', which plays an attenuated version of the current bus output with other parameters.

The only other option available in the effect is called 'dry'. The value of dry determines the output intensity of the original sound.

The only two important options for our use case is the dry and one of the taps ( say tap1 ). Notice that the Level db ( tap1/level_db ) option determines the output intensity of the tap. Hence setting it to -60 dB would give us silence.

Therefore, if we were to slide the 'dry' option around, the bus output would fade in or out. Empirically this method does not result in clipping or crackles, but it is still possible to get them if the volume change is nearly instantaneous in nature (such as with a volume slider), however these are nearly negligible and the end user is not very likely to notice. Even so, these can be easily avoided for the most part by using very small tweens on value change.

This workaround was hinted on by this question over here: https://godotengine.org/qa/27939/how-to-fade-in-out-an-audio-stream

@gianlluca
Copy link
Contributor

gianlluca commented Jul 28, 2023

I having this same problem in a Android export from Godot Engine 3.5.2.

At first I tried to use a Tween together with AudioServer.set_volume_db but I got several crackles in the audio.

So I came here and tried the solution mentioned by ideaplexer just above, but got the same crackles in the audio.

What worked for me was similar to ideaplexer workaround, but instead of using the Delay effect, I used Amplify.

Using tween to animate the volume_db property from Amplify effect doesn't cause the audio crackles for me, I don't know why, but if anyone is looking for a workaround, this solved the problem.

@hamoom
Copy link

hamoom commented Mar 7, 2024

I having this same problem in a Android export from Godot Engine 3.5.2.

At first I tried to use a Tween together with AudioServer.set_volume_db but I got several crackles in the audio.

So I came here and tried the solution mentioned by ideaplexer just above, but got the same crackles in the audio.

What worked for me was similar to ideaplexer workaround, but instead of using the Delay effect, I used Amplify.

Using tween to animate the volume_db property from Amplify effect doesn't cause the audio crackles for me, I don't know why, but if anyone is looking for a workaround, this solved the problem.

thanks this helped me. some sample code for godot 3

var bus_id = AudioServer.get_bus_index('SFX')
var effect = AudioServer.get_bus_effect(bus_id, 0)
var tween = create_tween()
tween.tween_property(effect, 'volume_db', -80, 1.0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants