This commit is contained in:
Gerard Gascón 2025-04-24 17:06:24 +02:00
commit 18efc36800
161 changed files with 5008 additions and 0 deletions

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-present Nathan Hoad
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,255 @@
using Godot;
using Godot.Collections;
namespace NathanHoad
{
public partial class SoundManager : Node
{
private static Node instance;
public static Node Instance
{
get
{
if (instance == null)
{
instance = (Node)Engine.GetSingleton("SoundManager");
}
return instance;
}
}
public static ProcessModeEnum SoundProcessMode
{
get => ((Node)Instance.Get("sound_effects")).ProcessMode;
set
{
((Node)Instance.Get("sound_effects")).ProcessMode = value;
}
}
public static ProcessModeEnum UISoundProcessMode
{
get => ((Node)Instance.Get("ui_sound_effects")).ProcessMode;
set
{
((Node)Instance.Get("ui_sound_effects")).ProcessMode = value;
}
}
public static ProcessModeEnum AmbientSoundProcessMode
{
get => ((Node)Instance.Get("ambient_sounds")).ProcessMode;
set
{
((Node)Instance.Get("ambient_sounds")).ProcessMode = value;
}
}
public static ProcessModeEnum MusicProcessMode
{
get => ((Node)Instance.Get("music")).ProcessMode;
set
{
((Node)Instance.Get("music")).ProcessMode = value;
}
}
#region Sounds
public static float GetSoundVolume()
{
return (float)Instance.Call("get_sound_volume");
}
public static float GetUISoundVolume()
{
return (float)Instance.Call("get_ui_sound_volume");
}
public static void SetSoundVolume(float volume)
{
Instance.Call("set_sound_volume", volume);
}
public static AudioStreamPlayer PlaySound(AudioStream resource, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_sound", resource, overrideBus);
}
public static AudioStreamPlayer PlaySoundWithPitch(AudioStream resource, float pitch, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_sound_with_pitch", resource, pitch, overrideBus);
}
public static void StopSound(AudioStream resource)
{
Instance.Call("stop_sound", resource);
}
public static AudioStreamPlayer PlayUISound(AudioStream resource, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_ui_sound", resource, overrideBus);
}
public static AudioStreamPlayer PlayUISoundWithPitch(AudioStream resource, float pitch, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_ui_sound_with_pitch", resource, pitch, overrideBus);
}
public static void StopUISound(AudioStream resource)
{
Instance.Call("stop_ui_sound", resource);
}
public static void SetDefaultSoundBus(string bus)
{
Instance.Call("set_default_sound_bus", bus);
}
public static void SetDefaultUISoundBus(string bus)
{
Instance.Call("set_default_ui_sound_bus", bus);
}
#endregion
#region Ambient sounds
public static AudioStreamPlayer PlayAmbientSound(AudioStream resource, float fadeInDuration, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_ambient_sound", resource, fadeInDuration);
}
public static void StopAmbientSound(AudioStream resource, float fadeOutDuration)
{
Instance.Call("stop_ambient_sound", resource, fadeOutDuration);
}
public static void StopAllAmbientSounds(float fadeOutDuration)
{
Instance.Call("stop_all_ambient_sounds", fadeOutDuration);
}
public static void SetDefaultAmbientSoundBus(string bus)
{
Instance.Call("set_default_ambient_sound_bus", bus);
}
#endregion
#region Music
public static float GetMusicVolume()
{
return (float)Instance.Call("get_music_volume");
}
public static void SetMusicVolume(float volume)
{
Instance.Call("set_music_volume", volume);
}
public static AudioStreamPlayer PlayMusic(AudioStream resource, float crossFadeDuration = 0.0f, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_music", resource, crossFadeDuration, overrideBus);
}
public static AudioStreamPlayer PlayMusicFromPosition(AudioStream resource, float position, float crossFadeDuration = 0.0f, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_music_from_position", resource, position, crossFadeDuration, overrideBus);
}
public static AudioStreamPlayer PlayMusicAtVolume(AudioStream resource, float volume, float crossFadeDuration = 0.0f, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_music_at_volume", resource, volume, crossFadeDuration, overrideBus);
}
public static AudioStreamPlayer PlayMusicFromPositionAtVolume(AudioStream resource, float position, float volume, float crossFadeDuration = 0.0f, string overrideBus = "")
{
return (AudioStreamPlayer)Instance.Call("play_music_from_position_at_volume", resource, position, volume, crossFadeDuration, overrideBus);
}
public static Array<string> GetMusicTrackHistory()
{
return (Array<string>)Instance.Call("get_music_track_history");
}
public static string GetLastPlayedMusicTrack()
{
return (string)Instance.Call("get_last_played_music_track");
}
public static bool IsMusicPlaying(AudioStream resource = null)
{
return (bool)Instance.Call("is_music_playing", resource);
}
public static bool IsMusicTrackPlaying(string resource_path)
{
return (bool)Instance.Call("is_music_track_playing", resource_path);
}
public static Array<AudioStream> GetCurrentlyPlayingMusic()
{
return (Array<AudioStream>)Instance.Call("get_currently_playing_music");
}
public static Array<string> GetCurrentlyPlayingTracks()
{
return (Array<string>)Instance.Call("get_currently_playing_tracks");
}
public static void PauseMusic(AudioStream resource = null)
{
Instance.Call("pause_music", resource);
}
public static void ResumeMusic(AudioStream resource = null)
{
Instance.Call("resume_music", resource);
}
public static void StopMusic(float fadeOutDuration = 0.0f)
{
Instance.Call("stop_music", fadeOutDuration);
}
public static void SetDefaultMusicBus(string bus)
{
Instance.Call("set_default_music_bus", bus);
}
#endregion
}
}

View file

@ -0,0 +1,148 @@
extends Node
@export var default_busses := []
@export var default_pool_size := 8
var available_players: Array[AudioStreamPlayer] = []
var busy_players: Array[AudioStreamPlayer] = []
var bus: String = "Master"
var _tweens: Dictionary = {}
func _init(possible_busses: PackedStringArray = default_busses, pool_size: int = default_pool_size) -> void:
bus = get_possible_bus(possible_busses)
for i in pool_size:
increase_pool()
func get_possible_bus(possible_busses: PackedStringArray) -> String:
for possible_bus in possible_busses:
var cases: PackedStringArray = [
possible_bus,
possible_bus.to_lower(),
possible_bus.to_camel_case(),
possible_bus.to_pascal_case(),
possible_bus.to_snake_case()
]
for case in cases:
if AudioServer.get_bus_index(case) > -1:
return case
return "Master"
func prepare(resource: AudioStream, override_bus: String = "") -> AudioStreamPlayer:
var player: AudioStreamPlayer
if resource is AudioStreamRandomizer:
player = get_player_with_resource(resource)
if player == null:
player = get_available_player()
player.stream = resource
player.bus = override_bus if override_bus != "" else bus
player.volume_db = linear_to_db(1.0)
player.pitch_scale = 1
return player
func get_available_player() -> AudioStreamPlayer:
if available_players.size() == 0:
increase_pool()
var player = available_players.pop_front()
busy_players.append(player)
return player
func get_player_with_resource(resource: AudioStream) -> AudioStreamPlayer:
for player in busy_players + available_players:
if player.stream == resource:
return player
return null
func get_busy_player_with_resource(resource: AudioStream) -> AudioStreamPlayer:
for player in busy_players:
if player.stream.resource_path == resource.resource_path:
return player
return null
func mark_player_as_available(player: AudioStreamPlayer) -> void:
if busy_players.has(player):
busy_players.erase(player)
if available_players.size() >= default_pool_size:
player.queue_free()
elif not available_players.has(player):
available_players.append(player)
func increase_pool() -> void:
# See if we can reclaim a rogue busy player
for player in busy_players:
if not player.playing:
mark_player_as_available(player)
return
# Otherwise, add a new player
var player := AudioStreamPlayer.new()
add_child(player)
available_players.append(player)
player.bus = bus
player.finished.connect(_on_player_finished.bind(player))
func fade_volume(player: AudioStreamPlayer, from_volume: float, to_volume: float, duration: float) -> AudioStreamPlayer:
# Remove any tweens that might already be on this player
_remove_tween(player)
# Start a new tween
var tween: Tween = get_tree().create_tween().bind_node(self)
player.volume_db = from_volume
if from_volume > to_volume:
# Fade out
tween.tween_property(player, "volume_db", to_volume, duration).set_trans(Tween.TRANS_CIRC).set_ease(Tween.EASE_IN)
else:
# Fade in
tween.tween_property(player, "volume_db", to_volume, duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
_tweens[player] = tween
tween.finished.connect(_on_fade_completed.bind(player, tween, from_volume, to_volume, duration))
return player
#region Helpers
func _remove_tween(player: AudioStreamPlayer) -> void:
if _tweens.has(player):
var fade: Tween = _tweens.get(player)
fade.kill()
_tweens.erase(player)
#endregion
#region Signals
func _on_player_finished(player: AudioStreamPlayer) -> void:
mark_player_as_available(player)
func _on_fade_completed(player: AudioStreamPlayer, tween: Tween, from_volume: float, to_volume: float, duration: float):
_remove_tween(player)
# If we just faded out then our player is now available
if to_volume <= -79.0:
player.stop()
mark_player_as_available(player)
#endregion

View file

@ -0,0 +1,30 @@
extends "./abstract_audio_player_pool.gd"
func play(resource: AudioStream, fade_in_duration: float, override_bus: String = "") -> AudioStreamPlayer:
var player = get_busy_player_with_resource(resource)
# If it's already playing then don't play it again
if is_instance_valid(player): return player
player = prepare(resource, override_bus)
fade_volume(player, -80.0, 0.0, fade_in_duration)
player.call_deferred("play")
return player
func stop(resource: AudioStream, fade_out_duration: float = 0.0) -> void:
if fade_out_duration <= 0.0:
fade_out_duration = 0.01
for player in busy_players:
if player.stream == resource:
fade_volume(player, player.volume_db, -80, fade_out_duration)
func stop_all(fade_out_duration: float = 0.0) -> void:
if fade_out_duration <= 0.0:
fade_out_duration = 0.01
for player in busy_players:
fade_volume(player, player.volume_db, -80, fade_out_duration)

View file

@ -0,0 +1,82 @@
extends "./abstract_audio_player_pool.gd"
var track_history: PackedStringArray = []
func play(resource: AudioStream, position: float = 0.0, volume: float = 0.0, crossfade_duration: float = 0.0, override_bus: String = "") -> AudioStreamPlayer:
stop(crossfade_duration * 2)
var player = get_busy_player_with_resource(resource)
# If the player already exists then just make sure the volume is right (it might have just been fading in or out)
if player != null:
fade_volume(player, player.volume_db, volume, crossfade_duration)
return player
# Otherwise we need to prep another player and handle its introduction
player = prepare(resource, override_bus)
fade_volume(player, -80.0, volume, crossfade_duration)
# Remember this track name
track_history.insert(0, resource.resource_path)
if track_history.size() > 50:
track_history.remove_at(50)
player.call_deferred("play", position)
return player
func is_playing(resource: AudioStream) -> bool:
if resource != null:
return get_busy_player_with_resource(resource) != null
else:
return busy_players.size() > 0
func stop(fade_out_duration: float = 0.0) -> void:
for player in busy_players:
if fade_out_duration <= 0.0:
fade_out_duration = 0.01
fade_volume(player, player.volume_db, -80, fade_out_duration)
func pause(resource: AudioStream = null) -> void:
if resource != null:
var player = get_busy_player_with_resource(resource)
if is_instance_valid(player):
player.stream_paused = true
else:
for player in busy_players:
player.stream_paused = true
func resume(resource: AudioStream = null) -> void:
if resource != null:
var player = get_busy_player_with_resource(resource)
if is_instance_valid(player):
player.stream_paused = false
else:
for player in busy_players:
player.stream_paused = false
func is_track_playing(resource_path: String) -> bool:
for player in busy_players:
if player.stream.resource_path == resource_path:
return true
return false
func get_currently_playing() -> Array[AudioStream]:
var tracks: Array[AudioStream] = []
for player in busy_players:
tracks.append(player.stream)
return tracks
func get_currently_playing_tracks() -> PackedStringArray:
var tracks: PackedStringArray = []
for player in busy_players:
tracks.append(player.stream.resource_path)
return tracks

View file

@ -0,0 +1,7 @@
[plugin]
name="SoundManager"
description="Manage your sounds and music"
author="Nathan Hoad"
version="2.6.2"
script="plugin.gd"

View file

@ -0,0 +1,14 @@
@tool
extends EditorPlugin
func _enter_tree():
add_autoload_singleton("SoundManager", get_plugin_path() + "/sound_manager.gd")
func _exit_tree():
remove_autoload_singleton("SoundManager")
func get_plugin_path() -> String:
return get_script().resource_path.get_base_dir()

View file

@ -0,0 +1,13 @@
extends "./abstract_audio_player_pool.gd"
func play(resource: AudioStream, override_bus: String = "") -> AudioStreamPlayer:
var player = prepare(resource, override_bus)
player.call_deferred("play")
return player
func stop(resource: AudioStream) -> void:
for player in busy_players:
if player.stream == resource:
player.call_deferred("stop")

View file

@ -0,0 +1,227 @@
extends Node
const SoundEffectsPlayer = preload("./sound_effects.gd")
const AmbientSoundsPlayer = preload("./ambient_sounds.gd")
const MusicPlayer = preload("./music.gd")
var sound_effects: SoundEffectsPlayer = SoundEffectsPlayer.new(["Sounds", "SFX"], 8)
var ui_sound_effects: SoundEffectsPlayer = SoundEffectsPlayer.new(["UI", "Interface", "Sounds", "SFX"], 8)
var ambient_sounds: AmbientSoundsPlayer = AmbientSoundsPlayer.new(["Sounds", "SFX"], 1)
var music: MusicPlayer = MusicPlayer.new(["Music"], 2)
var sound_process_mode: ProcessMode:
set(value):
sound_effects.process_mode = value
get:
return sound_effects.process_mode
var ui_sound_process_mode: ProcessMode:
set(value):
ui_sound_effects.process_mode = value
get:
return ui_sound_effects.process_mode
var ambient_sound_process_mode: ProcessMode:
set(value):
ambient_sounds.process_mode = value
get:
return ambient_sounds.process_mode
var music_process_mode: ProcessMode:
set(value):
music.process_mode = value
get:
return music.process_mode
func _init() -> void:
Engine.register_singleton("SoundManager", self)
add_child(sound_effects)
add_child(ui_sound_effects)
add_child(ambient_sounds)
add_child(music)
self.sound_process_mode = PROCESS_MODE_PAUSABLE
self.ui_sound_process_mode = PROCESS_MODE_ALWAYS
self.ambient_sound_process_mode = PROCESS_MODE_ALWAYS
self.music_process_mode = PROCESS_MODE_ALWAYS
#region Sounds
func get_sound_volume() -> float:
return db_to_linear(AudioServer.get_bus_volume_db(AudioServer.get_bus_index(sound_effects.bus)))
func set_sound_volume(volume_between_0_and_1: float) -> void:
_show_shared_bus_warning()
AudioServer.set_bus_volume_db(AudioServer.get_bus_index(sound_effects.bus), linear_to_db(volume_between_0_and_1))
func play_sound(resource: AudioStream, override_bus: String = "") -> AudioStreamPlayer:
return sound_effects.play(resource, override_bus)
func play_sound_with_pitch(resource: AudioStream, pitch: float = 1.0, override_bus: String = "") -> AudioStreamPlayer:
var player = sound_effects.play(resource, override_bus)
player.pitch_scale = pitch
return player
func stop_sound(resource: AudioStream) -> void:
return sound_effects.stop(resource)
func set_default_sound_bus(bus: String) -> void:
sound_effects.bus = bus
#endregion
#region UI sounds
func get_ui_sound_volume() -> float:
return db_to_linear(AudioServer.get_bus_volume_db(AudioServer.get_bus_index(ui_sound_effects.bus)))
func set_ui_sound_volume(volume_between_0_and_1: float) -> void:
_show_shared_bus_warning()
AudioServer.set_bus_volume_db(AudioServer.get_bus_index(ui_sound_effects.bus), linear_to_db(volume_between_0_and_1))
func play_ui_sound(resource: AudioStream, override_bus: String = "") -> AudioStreamPlayer:
return ui_sound_effects.play(resource, override_bus)
func play_ui_sound_with_pitch(resource: AudioStream, pitch: float = 1.0, override_bus: String = "") -> AudioStreamPlayer:
var player = ui_sound_effects.play(resource, override_bus)
player.pitch_scale = pitch
return player
func stop_ui_sound(resource: AudioStream) -> void:
return ui_sound_effects.stop(resource)
func set_default_ui_sound_bus(bus: String) -> void:
ui_sound_effects.bus = bus
#endregion
#region Ambient sound
func get_ambient_sound_volume() -> float:
return db_to_linear(AudioServer.get_bus_volume_db(AudioServer.get_bus_index(ambient_sounds.bus)))
func set_ambient_sound_volume(volume_between_0_and_1: float) -> void:
_show_shared_bus_warning()
AudioServer.set_bus_volume_db(AudioServer.get_bus_index(ambient_sounds.bus), linear_to_db(volume_between_0_and_1))
func play_ambient_sound(resource: AudioStream, fade_in_duration: float = 0.0, override_bus: String = "") -> AudioStreamPlayer:
return ambient_sounds.play(resource, fade_in_duration, override_bus)
func stop_ambient_sound(resource: AudioStream, fade_out_duration: float = 0.0) -> void:
ambient_sounds.stop(resource, fade_out_duration)
func stop_all_ambient_sounds(fade_out_duration: float = 0.0) -> void:
ambient_sounds.stop_all(fade_out_duration)
func set_default_ambient_sound_bus(bus: String) -> void:
ambient_sounds.bus = bus
#endregion
#region Music
func get_music_volume() -> float:
return db_to_linear(AudioServer.get_bus_volume_db(AudioServer.get_bus_index(music.bus)))
func set_music_volume(volume_between_0_and_1: float) -> void:
_show_shared_bus_warning()
AudioServer.set_bus_volume_db(AudioServer.get_bus_index(music.bus), linear_to_db(volume_between_0_and_1))
func play_music(resource: AudioStream, crossfade_duration: float = 0.0, override_bus: String = "") -> AudioStreamPlayer:
return music.play(resource, 0.0, 0.0, crossfade_duration, override_bus)
func play_music_from_position(resource: AudioStream, position: float = 0.0, crossfade_duration: float = 0.0, override_bus: String = "") -> AudioStreamPlayer:
return music.play(resource, position, 0.0, crossfade_duration, override_bus)
func play_music_at_volume(resource: AudioStream, volume: float = 0.0, crossfade_duration: float = 0.0, override_bus: String = "") -> AudioStreamPlayer:
return music.play(resource, 0.0, volume, crossfade_duration, override_bus)
func play_music_from_position_at_volume(resource: AudioStream, position: float = 0.0, volume: float = 0.0, crossfade_duration: float = 0.0, override_bus: String = "") -> AudioStreamPlayer:
return music.play(resource, position, volume, crossfade_duration, override_bus)
func get_music_track_history() -> Array:
return music.track_history
func get_last_played_music_track() -> String:
return music.track_history[0]
func is_music_playing(resource: AudioStream = null) -> bool:
return music.is_playing(resource)
func is_music_track_playing(resource_path: String) -> bool:
return music.is_track_playing(resource_path)
func get_currently_playing_music() -> Array:
return music.get_currently_playing()
func get_currently_playing_music_tracks() -> Array:
return music.get_currently_playing_tracks()
func pause_music(resource: AudioStream = null) -> void:
music.pause(resource)
func resume_music(resource: AudioStream = null) -> void:
music.resume(resource)
func stop_music(fade_out_duration: float = 0.0) -> void:
music.stop(fade_out_duration)
func set_default_music_bus(bus: String) -> void:
music.bus = bus
#endregion
#region helpers
func _show_shared_bus_warning() -> void:
if "Master" in [music.bus, sound_effects.bus, ui_sound_effects.bus, ambient_sounds.bus]:
push_warning("Using the Master sound bus directly isn't recommended.")
if music.bus == sound_effects.bus or music.bus == ui_sound_effects.bus:
push_warning("Both music and sounds are using the same bus: %s" % music.bus)
#endregion