-->
Besides allowing audio playback through stream player pools, there are some utilities to (easily) achieve:
Audio playback in Godot is rather easy, really. It consists in adding an AudioStreamPlayer
node, assigning an AudioStream
resource to it then calling the play()
function in the stream player node.
However there is a problem. What if the playback is done in a pickup item, or a projectile? Those are often destroyed (removed from the scene) as soon as they are "used". That is, a projectile hitting another object should trigger the audio playback, but the projectile also must be removed from the scene. When this removal happens, so does the AudioStreamPlayer
attached to it. As a result the playback ends. This means that at this point we need to take node lifetime into account.
The AudioMaster
plugin has been created precisely to allow us to easily playback audio without having to worry about node lifetime. Since I was already dealing with audio, I decided to add some extra functionalities too.
The AudioMaster
node manages "pools" of audio stream players. One pool for each configured audio bus. To start using this, obviously this node must be attached into the tree hierarchy. While there can be multiple instances of this node active, ideally only one should be used. The easiest way to achieve this is to create a "singleton node" (Project Settings -> Globals -> Autoload
) and attach one AudioMaster
into the node added into that auto load list. More information about this can be found in the singletons documentation . Keep in mind that the screenshots in the documentation are slightly outdated, but the information in there is still relevant.
Using the auto load feature means that all configuration done and pre allocation of the player pools will be preserved during scene changes. In the demo project the AudioMaster
is attached in a local scene, the one specific to demonstrate the plugin. For the discussion here, all snippets will consider that the singleton system is used, supposing the AudioMaster
is attached into a singleton named GameState
. If you want more detailed instructions on how to setup this specific aspect, expand the contents bellow. Otherwise just skip it to continue with how to use the plugin.
GameState
and save somewhere in your project.AudioMaster
into this scene.Project Settings -> Globals -> Autoload
.GameState
, which is how the Node will be accessed through code. Then click the Add button GameState
. You can now close the Project Settings window. GameState
node then add this line into it: @onready var audio_master: AudioMaster = $AudioMaster as AudioMaster
With this setup, accessing the AudioMaster
node through code will be as simple as GameState.audio_master.<some_function>
. The only problem is that Godot's auto complete does not work for the audio_master
property.
The first thing that must be done is configure the desired behavior of the management performed by the AudioMaster
. This setup includes which of the three possible stream players (AudioStreamPlayer
, AudioStreamPlayer2D
or AudioStreamPlayer3D
) should be created, the maximum amount of players to be allocated, how many to pre-allocate of those players as well as the audio volume. This setup is done in a "per audio bus basis".
Said configuration can be performed both through the Inspector or by code. The screenshot bellow shows how the Inspector (taken from the Demo project) looks like. Each configured audio bus will generate a subcategory within the Pre Config.
In this specific case, the Music audio bus has been setup to use the AudioStreamPlayer
, have a maximum of 2 of that node, pre-allocate both and set the audio volume to 50%. The Sfx 2d audio bus has been setup to use AudioStreamPlayer2D
nodes allowing a maximum of 16 of them.
When pre-allocation is not set, requesting to playback audio the AudioMaster
will automatically create the desired node type if the maximum hasn't been reached yet. If you don't want a limit you can set Max Players to 0
.
Now lets see how to perform that setup through code. To set the desired node type there is the set_player_type()
function. It requires two arguments. The first is the name of the audio bus. The second is the type, which is an option from the enum AudioMaster.PlayerType
. By default AudioMaster
will create AudioStreamPlayer
nodes, which correspond to AudioMaster.PlayerNormal
. While you can explicitly specify to use default values, it's not necessary to do so.
To set the maximum amount of players there is set_maximum_players()
function. It also requires two arguments. The first is the name of the audio bus and the second is the amount. Remember, providing 0
as argument removes the limit of stream player nodes to be created. By default AudioMaster
uses 32
as the maximum amount of player nodes.
To pre-allocate nodes there is the allocate_player()
function. It requires two arguments. As usual, the first one is the name of the audio bus, while the second is the amount to be created. Note that regardless of the amount given, if there is a limit configured (max_players > 0), then that limit will be respected.
For the moment I will skip the volume. The Process Mode specifies how the stream player nodes will behave depending on wether the game is paused or not. You can find more information about this mods in the process mode documentation . That said, to change this through code there is the set_player_process_mode()
function. It requires two arguments, the name of the audio bus and the process mode, which is an option from the Node.ProcessMode
enum. By default the mode is set to Inherit
.
Let's see in practice those mentioned functions (remember this is using the singleton setup that has been described above):
# Change the player type for nodes associated with SFX2D bus to be of AudioStreamPlayer2D
GameState.audio_master.set_player_type("SFX2D", AudioMaster.Player2D)
# Change the player type for nodes associated with SFX3D bus to be of AudioStreamPlayer3D
GameState.audio_master.set_player_type("SFX3D", AudioMaster.Player3D)
# Set maximum amount of AudioStreamPlayer2D nodes to be created for the SFX2D bus to 16
GameState.audio_master.set_maximum_players("SFX2D", 16)
# Set maximum amount of AudioStreamPlayer3D nodes to be created for the SFX3D bus to 42
GameState.audio_master.set_maximum_players("SFX3D", 42)
# Pre allocate all 16 nodes for the SFX2D bus
GameState.allocate_player("SFX2D", 16)
# Pre allocate 20 nodes for the SFX3D bus
GameState.allocate_player("SFX3D", 20)
# Change the UI player nodes to always play, even if game is paused
GameState.set_player_process_mode("UI", Node.PROCESS_MODE_ALWAYS)
This initialization could easily be done within the _ready()
function of the singleton script, meaning that it will be done only once.
NOTE: Calling those functions will overwrite any setup done within the Inspector.
Now lets deal with the audio volume. First to retrieve it, which is done with the function get_bus_volume_percent()
. It requires only one argument, the name of the audio bus. This function returns a float corresponding the the percent (in range [0..1]) set in that audio bus. Then set_bus_volume_percent()
can be used to change the volume. This function requires two arguments, the name of the audio bus and the volume, which must be in range [0..1]. There is a third optional argument, `during'. Basically it tells that the change in volume should occur during the specified amount of seconds. In practice:
# Set the volume for SFX2D to the maximum. This is instantaneous
GameState.audio_master.set_bus_volume_percent("SFX2D", 1.0)
# Set the volume for SFX3D to the maximum, but over 0.8 seconds
GameState.audio_master.set_bus_volume_percent("SFX3D", 1.0, 0.8)
# Retrieve the volume assigned in SFX2D audio bus. This can be them set in a Slider used to control the volume
var sfx2d_volume: float = GameState.audio_master.get_bus_volume_percent("SFX2D")
Now that AudioMaster
has been configured, there comes the actual audio playback. Requesting to play an audio stream can be done by using either play_audio()
or load_and_play()
.
Lets first see the play_audio()
function. It requires two arguments, the name of the audio bus and the AudioStream
resource. Additionally there are three optional arguments. The first optional argument allows to explicitly specify the index of the stream player node to be used. Then comes fade_time
, which tells, in seconds, the fade in duration. Finally there is a Dictionary that can be used to specify several other settings related to the playback. I believe the fade time is self explanatory. Shortly I will explain more about the player node index and the extra settings.
The load_and_play()
function works very similarly to the play_audio()
, however instead of providing an AudioStream
resource, it expects the path to an audio file. In practice:
# Suppose 'explosion' is an AudioStreamPlayer that has been previously loaded.
# Just play it using the SFX bus
GameState.audio_master.play_audio("SFX", explosion)
# Load and play an audio, also using SFX bus
GameState.audio_master.load_and_play("SFX", "res://resources/sfx/bleep.wav")
Now lets take a closer look at the player node index argument. When a negative value is provided AudioMaster
will attempt to find a "free player" and use it. For the most cases this is how audio playback will occur. Yet explicitly requesting a specific index allows cross fading to occur. The thing is, cross fading is an effect that fades out currently playing music while at the same time fading in the new one.
In order to fade out we obviously need means to stop the playback. This is done through stop()
function. It requires three arguments. First the name of the audio bus. The second argument is the index of the player node to be stopped. Finally the fade time, in seconds.
But then, ideally we should not blindly request to play an audio with an explicit index. It's possible to query AudioMaster
for such information. This can be done with get_available_player_index()
function. It requires a single argument, which is the name of the audio bus. It returns a non negative value if there is any stream player that can be used.
To show that in practice I will explain how to perform cross fading using AudioMaster
. At least two player nodes are required. If you check the "Inspector" screenshot above, you may notice that the setup for Music bus was set to have a maximum of two player nodes while also pre allocating both of them.
In the script meant to perform the effect, a variable is required. This variable is meant to track down which index is currently in use. In the snippet this will be called currently_active
. It might be initialized with -1
to tell that no node is being used.
So, when requesting to playback an audio, the first thing is to check if there is one already being played, which is basically checking if currently_active
is positive or not. So when something is already being played, we can call stop()
providing the value of currently_active
as the player index. The next step is to request the index of an available player, possibly directly assigning it into the currently_active
variable. We can double check if the returned value is positive (meaning that there is an available player) then request to playback the next audio using the obtained value. So, let's see the snippet of code performing that:
# Remember this has been initialized with -1
if (currently_active >= 0):
GameState.audio_master.stop("Music", currently_active, 0.8)
# Request a free audio player
currently_active = GameState.audio_master.get_available_player_index("Music")
# Then play the new audio if there is an available player
if (currently_active >= 0):
# Assume 'boss_battle' is a variable holding an AudioStream resource
GameState.audio_master.play("Music", boss_battle, currently_active, 0.8)
As you can seen, achieving cross fading effect is rather easy. Of course, you can add several other logic to better control the flow and which audio is actually playing. As an example, there is no sense is performing cross fading from and to the exact same audio file. Another aspect to keep in mind is that the fading in the snippet takes 0.8 seconds to finish. This means that in this tiny time interval attempting to perform another cross fading might fail since both stream player nodes are in use. Then there is the fact that when the music finishes playing, it might be a good idea to reset the currently_active
variable. The AudioMaster
node emits the playback_finished
signal when that occurs. It gives two arguments, the name of the bus corresponding to the player node that has just finished its playback and the index of the stream player node.
Now let's see the extra
argument of the playback functions. As mentioned, it's a Dictionary meant to hold additional setup, such as position of the stream player node, attenuation and so on. Basically each key in this dictionary represents an additional setup that can be performed. The associated value depends on the key itself. All entries are optional and, if not provided, a default value will be used.
In this first table, the listed keys corresponds to the additional settings that are somewhat "common" (with the exception of the position, those are available in all player types):
Key | Default Value | Description |
---|---|---|
start_from | 0 | Begin the playback of the audio from this position in seconds |
pitch_scale | 1.0 | The pitch and the tempo of the audio, as a multiplier of the sample rate |
When the bus is configured to use AudioStreamPlayer
(or type = AudioMaster.PlayerNormal
) then there is a single specific additional setting. The table bellow shows the additional settings that can be applied when the playback uses type = AudioMaster.PlayerNormal
. For more information on some of those properties refer to the AudioStreamPlayer
documentation .
Key | Default Value | Description |
---|---|---|
mix_target | MIX_TARGET_STEREO | Assign the mix_target property of the AudioStreamPlayer node. Possible values areMIX_TARGET_STEREO MIX_TARGET_SURROUND MIX_TARGET_CENTER |
The next table contains the list of keys that can be used as additional settings for AudioStreamPlayer2D
nodes, or when the corresponding audio bus has been configured in the AudioMaster
to use type = AudioMaster.Player2D
:
Key | Default Value | Description |
---|---|---|
position | Vector2() | Specify the global position that will be assigned into the stream player node just before the playback. |
area_mask | 1 | Areas in which the sound plays |
attenuation | 1.0 | Dampens the audio over a distance with this as an exponent |
max_distance | 2000.0 | Maximum distance from which audio is still hearable |
panning_strength | 1.0 | Scales the panning strength. Higher values will pan audio from left to right more dramatically than lower values |
And in the next table the list of settings used in the AudioStreamPlayer3D
nodes, or when the corresponding audio bus has been configured in the AudioMaster
to be type = AudioMaster.Player3D
:
Key | Default Value | Description |
---|---|---|
position | Vector3() | Specify the global position that will be assigned into the stream player node just before the playback. |
area_mask | 1 | Determines which Area3D layers affect the sound for reverb and audio bus effects |
attenuation_filter_cutoff_hz | 5000.0 | A sound above the specified frequency is attenuated more than one bellow it. To disable this effect set this to 20500 |
attenuation_filter_db | -24.0 | How much, in decibels, the attenuation filter will affect loudness |
attenuation_model | ATTENUATION_INVERSE_DISTANCE | How the attenuation will be done. Can be ATTENUATION_INVERSE_DISTANCE ATTENUATION_INVERSE_SQUARE_DISTANCE ATTENUATION_LOGARITHMIC ATTENUATION_DISABLED |
doppler_tracking | DOPPLER_TRACKING_DISABLED | In which step the Doppler effect should be calculated. Can beDOPPLER_TRACKING_DISABLED DOPPLER_TRACKING_IDLE_STEP DOPPLER_TRACKING_PHYSICS_STEP |
emission_angle_degrees | 45.0 | The angle in which the audio reaches a listener unattenuated |
emission_angle_enabled | false | If true the audio should be attenuated according to the direction of the sound |
emission_angle_filter_attenuation_db | -12.0 | Attenuation factor used if listener is outside of emission_angle_degrees and emission_angle_enabled is true |
max_db | 3.0 | The absolute maximum of the sound level |
max_distance | 0.0 | Maximum distance from which audio is still hearable |
panning_strength | 1.0 | Scales the panning strength. Higher values will pan audio from left to right more dramatically than lower values |
unit_size | 10.0 | The factor for the attenuation effect. Higher values make the sound audible over a larger distance |
We can also query if a specific player node is currently playing or not. This can be done through is_playing()
or is_used()
. Both functions require the same arguments, audio bus name and stream player node index. There is an internal slight difference between the two functions. The is_playing()
calls the AudioStreamPlayer
node's is_playing()
. However the is_used()
queries internal upkeep data that tracks which nodes are "marked" as available. There is a very small chance that calling is_playing()
might return false while is_used()
return true.
How about "seeking", that is, requesting to playback from a specific location? For that there is the set_playback_position()
function. It requires three arguments. The audio bus name, the stream player node index and the position, in seconds, to be set.
Querying current position is possible by calling get_playback_position()
, which requires two arguments. The audio bus name and the stream player node index.
The AudioStream
resource contains a get_length()
function that provides the duration of the audio file. This can be used if you want to create a slider (or progress bar) displaying the progress of currently playing audio.
There are another two functions used to stop audio playback. The first one that I want to mention is stop_all_in_bus()
. This function requires the bus name as its first argument. Optionally the second argument is used to created a fade out effect, in seconds. That said, this function stops all playback in a given bus.
Then there is the stop_all()
. Its only argument is optional and is used to create a fade out effect, in seconds. That said, this will request all stream player nodes associated with all audio buses to be stopped.
The AudioMaster
node contains a function (get_details_info()
) that can be used to obtain debugging information. It returns an array of dictionaries. More specifically, one dictionary for each audio bus. The contents of each dictionary are:
Key | What |
---|---|
bus | Name of the associated audio bus |
player_count | Total amount of audio stream player nodes that have been created for the audio bus |
playing | How many of those nodes are currently playing an audio stream |
available | Howe many of the player_count is available to be used |
type | A string that is either Normal, 2D or 3D |