Initial commit

This commit is contained in:
Bad Manners 2024-03-14 00:53:46 -03:00
commit 7becdd23b6
989 changed files with 28526 additions and 0 deletions

View file

@ -0,0 +1,38 @@
class_name NullPooledAudioStreamPlayer
extends PooledAudioStreamPlayer
## An extension of PooledAudioStreamPlayer that nerfs all of its public methods.
## Whether this player is a [PooledAudioStreamPlayer], or a Null instance.
func is_null() -> bool:
return true
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.trigger]
func trigger() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.trigger_varied]
func trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.reset_volume]
func reset_volume() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.reset_pitch]
func reset_pitch() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.reset_all]
func reset_all() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.release]
func release(p_finish_playing: bool = false) -> void:
return

View file

@ -0,0 +1,38 @@
class_name NullPooledAudioStreamPlayer2D
extends PooledAudioStreamPlayer2D
## An extension of PooledAudioStreamPlayer2D that nerfs all of its public methods.
## Whether this player is a [PooledAudioStreamPlayer2D], or a Null instance.
func is_null() -> bool:
return true
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.trigger]
func trigger() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.trigger_varied]
func trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.reset_volume]
func reset_volume() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.reset_pitch]
func reset_pitch() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.reset_all]
func reset_all() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.release]
func release(p_finish_playing: bool = false) -> void:
return

View file

@ -0,0 +1,38 @@
class_name NullPooledAudioStreamPlayer3D
extends PooledAudioStreamPlayer3D
## An extension of PooledAudioStreamPlayer3D that nerfs all of its public methods.
## Whether this player is a [PooledAudioStreamPlayer3D], or a Null instance.
func is_null() -> bool:
return true
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.trigger]
func trigger() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.trigger_varied]
func trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.reset_volume]
func reset_volume() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.reset_pitch]
func reset_pitch() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.reset_all]
func reset_all() -> void:
return
## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.release]
func release(p_finish_playing: bool = false) -> void:
return

View file

@ -0,0 +1,158 @@
class_name PoolEntity
extends RefCounted
## An abstract/static class to house all of the common PooledAudioStreamPlayer* functionality.
const ResonateSettings = preload("../shared/resonate_settings.gd")
enum FollowType {DISABLED, IDLE, PHYSICS}
## Create a new PooledAudioStreamPlayer*.
static func create(p_base) -> Variant:
p_base.process_mode = Node.PROCESS_MODE_ALWAYS
return p_base
## Configure a PooledAudioStreamPlayer*.
static func configure(p_base, p_streams: Array, p_reserved: bool, p_bus: String, p_poly: bool, p_volume: float, p_pitch: float, p_mode: Node.ProcessMode) -> bool:
p_base.streams = p_streams
p_base.poly = p_poly
p_base.bus = p_bus
p_base.process_mode = p_mode
p_base.reserved = p_reserved
p_base.releasing = false
p_base.volume_db = p_volume if not p_poly else 0.0
p_base.pitch_scale = p_pitch if not p_poly else 1.0
p_base.base_volume = p_volume
p_base.base_pitch = p_pitch
p_base.follow_target = null
p_base.follow_type = FollowType.DISABLED
if not p_base.poly:
return false
var _settings = ResonateSettings.new()
var max_polyphony = ProjectSettings.get_setting(
_settings.MAX_POLYPHONY_SETTING_NAME,
_settings.MAX_POLYPHONY_SETTING_DEFAULT)
p_base.stream = AudioStreamPolyphonic.new()
p_base.max_polyphony = max_polyphony
p_base.stream.polyphony = max_polyphony
return true
## Attach a PooledAudioStreamPlayer* to a position or node.
static func attach_to(p_base, p_node: Variant) -> void:
if p_node == null:
return
if ResonateUtils.is_vector(p_node):
p_base.global_position = p_node
if ResonateUtils.is_node(p_node):
p_base.follow_target = p_node
p_base.follow_type = FollowType.IDLE
## Sync a PooledAudioStreamPlayer*'s transform with its target's when applicable.
static func sync_process(p_base) -> void:
if p_base.follow_target == null:
return
if not is_instance_valid(p_base.follow_target):
return
if p_base.follow_type != FollowType.IDLE:
return
p_base.global_position = p_base.follow_target.global_position
## Sync a PooledAudioStreamPlayer*'s transform with its target's
## when applicable during the physics step.
static func sync_physics_process(p_base) -> void:
if p_base.follow_target == null:
return
if not is_instance_valid(p_base.follow_target):
return
if p_base.follow_type != FollowType.PHYSICS:
return
p_base.global_position = p_base.follow_target.global_position
## Trigger a PooledAudioStreamPlayer*.
static func trigger(p_base, p_varied: bool, p_pitch: float, p_volume: float) -> bool:
if p_base.streams.size() == 0:
push_warning("Resonate - The player [%s] does not contain any streams, ensure you're using the SoundManager to instance it correctly." % p_base.name)
return false
var next_stream = p_base.streams.pick_random()
if not p_base.poly and p_varied:
p_base.volume_db = p_volume
p_base.pitch_scale = p_pitch
if not p_base.poly:
p_base.stream = next_stream
return true
var playback = p_base.get_stream_playback() as AudioStreamPlaybackPolyphonic
if p_varied:
playback.play_stream(next_stream, 0, p_volume, p_pitch)
else:
playback.play_stream(next_stream, 0, p_base.base_volume, p_base.base_pitch)
return false
## Reset the volume of a PooledAudioStreamPlayer*.
static func reset_volume(p_base) -> void:
p_base.volume_db = p_base.base_volume
## Reset the pitch of a PooledAudioStreamPlayer*.
static func reset_pitch(p_base) -> void:
p_base.pitch_scale = p_base.base_pitch
## Reset both the volume and pitch of a PooledAudioStreamPlayer*.
static func reset_all(p_base) -> void:
p_base.volume_db = p_base.base_volume
p_base.pitch_scale = p_base.base_pitch
## Release a PooledAudioStreamPlayer* back into the pool.
static func release(p_base, p_finish_playing: bool) -> void:
if p_base.releasing:
return
var has_loops = p_base.streams.any(ResonateUtils.is_stream_looped)
if p_finish_playing and has_loops:
push_warning("Resonate - The player [%s] has looping streams and therefore will never release itself back to the pool (as playback continues indefinitely). It will be forced to stop." % p_base.name)
p_base.stop()
if not p_finish_playing:
p_base.stop()
p_base.reserved = false
p_base.process_mode = Node.PROCESS_MODE_ALWAYS
p_base.releasing = true
p_base.released.emit()
## A callback to release a PooledAudioStreamPlayer* when it finishes playing.
static func finished(p_base) -> void:
if p_base.reserved:
return
p_base.release()

View file

@ -0,0 +1,124 @@
class_name PooledAudioStreamPlayer
extends AudioStreamPlayer
## An extension of AudioStreamPlayer that manages sequential and
## polyphonic playback as part of a pool of players.
## Emitted when this player has been released and should return to the pool.
signal released
## Whether this player has been reserved.
var reserved: bool
## Whether this player is in the process of being released.
var releasing: bool
## Whether this player has been configured to support polyphonic playback.
var poly: bool
## The collection of streams configured on this player.
var streams: Array
## The base/fallback volume of this player.
var base_volume: float
## The base/fallback pitch of this player.
var base_pitch: float
## The target this player should follow in 2D or 3D space.
var follow_target: Node
## When the player should sync its transform when following a target.
var follow_type: PoolEntity.FollowType
# ------------------------------------------------------------------------------
# Lifecycle methods
# ------------------------------------------------------------------------------
func _ready() -> void:
finished.connect(_on_finished)
func _process(_p_delta) -> void:
PoolEntity.sync_process(self)
func _physics_process(_p_delta) -> void:
PoolEntity.sync_physics_process(self)
# ------------------------------------------------------------------------------
# Public methods
# ------------------------------------------------------------------------------
## Returns a new player.
static func create() -> PooledAudioStreamPlayer:
return PoolEntity.create(PooledAudioStreamPlayer.new())
## Whether this player is a [NullPooledAudioStreamPlayer], or real instance.
func is_null() -> bool:
return false
## Configure this player with the given streams and charateristics.
func configure(p_streams: Array, p_reserved: bool, p_bus: String, p_poly: bool, p_volume: float, p_pitch: float, p_mode: Node.ProcessMode) -> void:
var is_polyphonic = PoolEntity.configure(self, p_streams, p_reserved, p_bus, p_poly, p_volume, p_pitch, p_mode)
if is_polyphonic:
super.play()
## Attach this player to a 2D/3D position or node.
func attach_to(p_node: Variant) -> void:
PoolEntity.attach_to(self, p_node)
## Trigger (play) a random variation associated with this player.
func trigger() -> void:
var should_play = PoolEntity.trigger(self, false, 1.0, 0.0)
if should_play:
super.play()
## Trigger (play) a random variation associated with this
## player with the given volume and pitch settings.
func trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:
var should_play = PoolEntity.trigger(self, true, p_pitch, p_volume)
if should_play:
super.play()
## Reset the volume of this player back to the default set in its bank.
func reset_volume() -> void:
PoolEntity.reset_volume(self)
## Reset the pitch of this player back to the default set in its bank.
func reset_pitch() -> void:
PoolEntity.reset_pitch(self)
## Reset both the volume and pitch of this player back to the default set in its bank.
func reset_all() -> void:
PoolEntity.reset_all(self)
## Release this player back to the pool, and optionally
## wait for it to finish playing before doing so.
func release(p_finish_playing: bool = false) -> void:
PoolEntity.release(self, p_finish_playing)
# ------------------------------------------------------------------------------
# Private methods
# ------------------------------------------------------------------------------
func _on_finished() -> void:
PoolEntity.finished(self)

View file

@ -0,0 +1,124 @@
class_name PooledAudioStreamPlayer2D
extends AudioStreamPlayer2D
## An extension of AudioStreamPlayer2D that manages sequential and
## polyphonic playback as part of a pool of players.
## Emitted when this player has been released and should return to the pool.
signal released
## Whether this player has been reserved.
var reserved: bool
## Whether this player is in the process of being released.
var releasing: bool
## Whether this player has been configured to support polyphonic playback.
var poly: bool
## The collection of streams configured on this player.
var streams: Array
## The base/fallback volume of this player.
var base_volume: float
## The base/fallback pitch of this player.
var base_pitch: float
## The target this player should follow in 2D or 3D space.
var follow_target: Node
## When the player should sync its transform when following a target.
var follow_type: PoolEntity.FollowType
# ------------------------------------------------------------------------------
# Lifecycle methods
# ------------------------------------------------------------------------------
func _ready() -> void:
finished.connect(_on_finished)
func _process(_p_delta) -> void:
PoolEntity.sync_process(self)
func _physics_process(_p_delta) -> void:
PoolEntity.sync_physics_process(self)
# ------------------------------------------------------------------------------
# Public methods
# ------------------------------------------------------------------------------
## Returns a new player.
static func create() -> PooledAudioStreamPlayer2D:
return PoolEntity.create(PooledAudioStreamPlayer2D.new())
## Whether this player is a [NullPooledAudioStreamPlayer2D], or real instance.
func is_null() -> bool:
return false
## Configure this player with the given streams and charateristics.
func configure(p_streams: Array, p_reserved: bool, p_bus: String, p_poly: bool, p_volume: float, p_pitch: float, p_mode: Node.ProcessMode) -> void:
var is_polyphonic = PoolEntity.configure(self, p_streams, p_reserved, p_bus, p_poly, p_volume, p_pitch, p_mode)
if is_polyphonic:
super.play()
## Attach this player to a 2D/3D position or node.
func attach_to(p_node: Variant) -> void:
PoolEntity.attach_to(self, p_node)
## Trigger (play) a random variation associated with this player.
func trigger() -> void:
var should_play = PoolEntity.trigger(self, false, 1.0, 0.0)
if should_play:
super.play()
## Trigger (play) a random variation associated with this
## player with the given volume and pitch settings.
func trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:
var should_play = PoolEntity.trigger(self, true, p_pitch, p_volume)
if should_play:
super.play()
## Reset the volume of this player back to the default set in its bank.
func reset_volume() -> void:
PoolEntity.reset_volume(self)
## Reset the pitch of this player back to the default set in its bank.
func reset_pitch() -> void:
PoolEntity.reset_pitch(self)
## Reset both the volume and pitch of this player back to the default set in its bank.
func reset_all() -> void:
PoolEntity.reset_all(self)
## Release this player back to the pool, and optionally
## wait for it to finish playing before doing so.
func release(p_finish_playing: bool = false) -> void:
PoolEntity.release(self, p_finish_playing)
# ------------------------------------------------------------------------------
# Private methods
# ------------------------------------------------------------------------------
func _on_finished() -> void:
PoolEntity.finished(self)

View file

@ -0,0 +1,124 @@
class_name PooledAudioStreamPlayer3D
extends AudioStreamPlayer3D
## An extension of AudioStreamPlayer3D that manages sequential and
## polyphonic playback as part of a pool of players.
## Emitted when this player has been released and should return to the pool.
signal released
## Whether this player has been reserved.
var reserved: bool
## Whether this player is in the process of being released.
var releasing: bool
## Whether this player has been configured to support polyphonic playback.
var poly: bool
## The collection of streams configured on this player.
var streams: Array
## The base/fallback volume of this player.
var base_volume: float
## The base/fallback pitch of this player.
var base_pitch: float
## The target this player should follow in 2D or 3D space.
var follow_target: Node
## When the player should sync its transform when following a target.
var follow_type: PoolEntity.FollowType
# ------------------------------------------------------------------------------
# Lifecycle methods
# ------------------------------------------------------------------------------
func _ready() -> void:
finished.connect(_on_finished)
func _process(_p_delta) -> void:
PoolEntity.sync_process(self)
func _physics_process(_p_delta) -> void:
PoolEntity.sync_physics_process(self)
# ------------------------------------------------------------------------------
# Public methods
# ------------------------------------------------------------------------------
## Returns a new player.
static func create() -> PooledAudioStreamPlayer3D:
return PoolEntity.create(PooledAudioStreamPlayer3D.new())
## Whether this player is a [NullPooledAudioStreamPlayer3D], or real instance.
func is_null() -> bool:
return false
## Configure this player with the given streams and charateristics.
func configure(p_streams: Array, p_reserved: bool, p_bus: String, p_poly: bool, p_volume: float, p_pitch: float, p_mode: Node.ProcessMode) -> void:
var is_polyphonic = PoolEntity.configure(self, p_streams, p_reserved, p_bus, p_poly, p_volume, p_pitch, p_mode)
if is_polyphonic:
super.play()
## Attach this player to a 2D/3D position or node.
func attach_to(p_node: Variant) -> void:
PoolEntity.attach_to(self, p_node)
## Trigger (play) a random variation associated with this player.
func trigger() -> void:
var should_play = PoolEntity.trigger(self, false, 1.0, 0.0)
if should_play:
super.play()
## Trigger (play) a random variation associated with this
## player with the given volume and pitch settings.
func trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:
var should_play = PoolEntity.trigger(self, true, p_pitch, p_volume)
if should_play:
super.play()
## Reset the volume of this player back to the default set in its bank.
func reset_volume() -> void:
PoolEntity.reset_volume(self)
## Reset the pitch of this player back to the default set in its bank.
func reset_pitch() -> void:
PoolEntity.reset_pitch(self)
## Reset both the volume and pitch of this player back to the default set in its bank.
func reset_all() -> void:
PoolEntity.reset_all(self)
## Release this player back to the pool, and optionally
## wait for it to finish playing before doing so.
func release(p_finish_playing: bool = false) -> void:
PoolEntity.release(self, p_finish_playing)
# ------------------------------------------------------------------------------
# Private methods
# ------------------------------------------------------------------------------
func _on_finished() -> void:
PoolEntity.finished(self)

View file

@ -0,0 +1,17 @@
class_name SoundBank
extends Node
## A container used to store & group sound events in your scene.
## This bank's unique identifier.
@export var label: String
## The bus to use for all sound events in this bank.[br][br]
## [b]Note:[/b] this will override the bus set in your project settings (Audio/Manager/Sound/Bank)
@export var bus: String
## The underlying process mode for all sound events in this bank.
@export var mode: Node.ProcessMode
## The collection of sound events associated with this bank.
@export var events: Array[SoundEventResource]

View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16">
<g clip-path="url(#a)">
<path fill="url(#b)" d="M14 0H2a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2ZM4.47 11.18a1.19 1.19 0 0 1-1.62.46 1.19 1.19 0 0 1-.47-1.62c.36-.65.55-1.33.55-2.02a4.1 4.1 0 0 0-.55-2.03 1.2 1.2 0 0 1 .47-1.62 1.2 1.2 0 0 1 1.62.46 6.4 6.4 0 0 1 0 6.36v.01Zm3.88 1.37a1.2 1.2 0 0 1-1.62.46 1.2 1.2 0 0 1-.45-1.62 6.91 6.91 0 0 0 0-6.76 1.2 1.2 0 0 1 .45-1.62 1.2 1.2 0 0 1 1.62.46 9.3 9.3 0 0 1 0 9.1v-.02Zm3.85 1.35a1.2 1.2 0 0 1-1.63.45 1.16 1.16 0 0 1-.56-.72c-.08-.31-.05-.63.11-.9a9.58 9.58 0 0 0 0-9.46c-.16-.28-.2-.6-.11-.9.08-.31.28-.56.56-.72a1.2 1.2 0 0 1 1.62.45 12 12 0 0 1 0 11.8h.01Z"/>
</g>
<defs>
<linearGradient id="b" x1="2.9" x2="13.1" y1="16.83" y2="-.83" gradientUnits="userSpaceOnUse">
<stop stop-color="#9F9"/>
<stop offset=".5" stop-color="#E6D767"/>
<stop offset="1" stop-color="#EA6C5E"/>
</linearGradient>
<clipPath id="a">
<path fill="#fff" d="M0 0h16v16H0z"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ntgipyp6jv34"
path="res://.godot/imported/sound_bank.svg-d1b3f43714c54a9c122e6a364a95c7d8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/resonate/sound_manager/sound_bank.svg"
dest_files=["res://.godot/imported/sound_bank.svg-d1b3f43714c54a9c122e6a364a95c7d8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,21 @@
class_name SoundEventResource
extends Resource
## The container used to store the details of a sound event.
## This sound event's unique identifier.
@export var name: String = ""
## The bus to use for all sound events in this bank.[br][br]
## [b]Note:[/b] this will override the bus set in this events sound bank,
## or your project settings (Audio/Manager/Sound/Bank)
@export var bus: String = ""
## The volume of the sound event.
@export_range(-80.0, 6.0, 0.1, "suffix:dB") var volume: float = 0.0
## The pitch of the sound event.
@export var pitch: float = 1.0
## The collection of audio streams (variations) associated with this sound event.
@export var streams: Array[AudioStream]

View file

@ -0,0 +1,493 @@
extends Node
## The SoundManager is responsible for all sound events in your game.
##
## It manages pools of 1D, 2D, and 3D audio stream players, which can be used
## for single-shot sound events, or reserved by scripts for repetitive & exclusive use.
## Sound events can contain many variations which will be chosen and played at random.
## Playback can be achieved both sequentially and polyphonically.
##
## @tutorial(View example scenes): https://github.com/hugemenace/resonate/tree/main/examples
const ResonateSettings = preload("../shared/resonate_settings.gd")
var _settings = ResonateSettings.new()
## Emitted only once when the SoundManager has finished setting up and
## is ready to play or instance sound events.
signal loaded
## Emitted every time the SoundManager detects that a SoundBank has
## been added or removed from the scene tree.
signal banks_updated
## Emitted every time one of the player pools is updated.
signal pools_updated
## Emitted whenever [signal SoundManager.loaded], [signal SoundManager.banks_updated],
## or [signal SoundManager.pools_updated] is emitted.
signal updated
## Whether the SoundManager has completed setup and is ready to play or instance sound events.
var has_loaded: bool = false
var _1d_players: Array[PooledAudioStreamPlayer] = []
var _2d_players: Array[PooledAudioStreamPlayer2D] = []
var _3d_players: Array[PooledAudioStreamPlayer3D] = []
var _event_table: Dictionary = {}
var _event_table_hash: int
# ------------------------------------------------------------------------------
# Lifecycle methods
# ------------------------------------------------------------------------------
func _init():
process_mode = Node.PROCESS_MODE_ALWAYS
func _ready() -> void:
_initialise_pool(ProjectSettings.get_setting(
_settings.POOL_1D_SIZE_SETTING_NAME,
_settings.POOL_1D_SIZE_SETTING_DEFAULT),
_create_player_1d)
_initialise_pool(ProjectSettings.get_setting(
_settings.POOL_2D_SIZE_SETTING_NAME,
_settings.POOL_2D_SIZE_SETTING_DEFAULT),
_create_player_2d)
_initialise_pool(ProjectSettings.get_setting(
_settings.POOL_3D_SIZE_SETTING_NAME,
_settings.POOL_3D_SIZE_SETTING_DEFAULT),
_create_player_3d)
_auto_add_events()
var scene_root = get_tree().root.get_tree()
scene_root.node_added.connect(_on_scene_node_added)
scene_root.node_removed.connect(_on_scene_node_removed)
func _process(_p_delta) -> void:
if _event_table_hash != _event_table.hash():
_event_table_hash = _event_table.hash()
banks_updated.emit()
updated.emit()
if has_loaded:
return
has_loaded = true
loaded.emit()
updated.emit()
# ------------------------------------------------------------------------------
# Public methods
# ------------------------------------------------------------------------------
## Returns a new Null player (null object pattern) which mimics a [PooledAudioStreamPlayer],
## allowing you to call methods such as [method PooledAudioStreamPlayer.trigger]
## without the need to wrap the call in a null check.
func null_instance() -> NullPooledAudioStreamPlayer:
return NullPooledAudioStreamPlayer.new()
## Returns a new Null player (null object pattern) which mimics a [PooledAudioStreamPlayer2D],
## allowing you to call methods such as [method PooledAudioStreamPlayer2D.trigger]
## without the need to wrap the call in a null check.
func null_instance_2d() -> NullPooledAudioStreamPlayer2D:
return NullPooledAudioStreamPlayer2D.new()
## Returns a new Null player (null object pattern) which mimics a [PooledAudioStreamPlayer3D],
## allowing you to call methods such as [method PooledAudioStreamPlayer3D.trigger]
## without the need to wrap the call in a null check.
func null_instance_3d() -> NullPooledAudioStreamPlayer3D:
return NullPooledAudioStreamPlayer3D.new()
## Used to determine whether the given [b]p_instance[/b] variable can be instantiated. It will return
## true if the SoundManager hasn't loaded yet, if the instance is already instantiated,
## or if the instance has been instantiated but is currently being released.
func should_skip_instancing(p_instance) -> bool:
if not has_loaded:
return true
if p_instance != null and p_instance.releasing:
return true
if p_instance != null and not p_instance.is_null():
return true
return false
## This a shorthand method used to instantiate a new instance while optionally configuring it
## to be automatically released when the given [b]p_base[/b] is removed from the scene tree.[br][br]
## The [b]p_factory[/b] callable is used to create the instance required. See example below:[br][br]
## [codeblock]
## _instance_note_one = SoundManager.quick_instance(_instance_note_one,
## SoundManager.instance.bind("example", "one"), self)
## [/codeblock]
func quick_instance(p_instance, p_factory: Callable, p_base: Node = null, p_finish_playing: bool = false) -> Variant:
if should_skip_instancing(p_instance):
return
var new_instance = p_factory.call()
if p_base != null:
release_on_exit(p_base, new_instance, p_finish_playing)
return new_instance
## Play a sound event from a SoundBank.
func play(p_bank_label: String, p_event_name: String, p_bus: String = "") -> void:
var instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, null)
instance.trigger()
instance.release(true)
## Play a sound event from a SoundBank at a specific [b]Vector2[/b] or [b]Vector3[/b] position.
func play_at_position(p_bank_label: String, p_event_name: String, p_position, p_bus: String = "") -> void:
var instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, p_position)
instance.trigger()
instance.release(true)
## Play a sound event from a SoundBank on a [b]Node2D[/b] or [b]Node3D[/b]. This causes the sound to
## synchronise with the Node's global position - causing it to move in 2D or 3D space along with the Node.
func play_on_node(p_bank_label: String, p_event_name: String, p_node, p_bus: String = "") -> void:
var instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, p_node)
instance.trigger()
instance.release(true)
## Play a sound event from a SoundBank with the provided pitch and/or volume.
func play_varied(p_bank_label: String, p_event_name: String, p_pitch: float = 1.0, p_volume: float = 0.0, p_bus: String = "") -> void:
var instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, null)
instance.trigger_varied(p_pitch, p_volume)
instance.release(true)
## Play a sound event from a SoundBank at a specific [b]Vector2[/b] or [b]Vector3[/b]
## position with the provided pitch and/or volume.
func play_at_position_varied(p_bank_label: String, p_event_name: String, p_position, p_pitch: float = 1.0, p_volume: float = 0.0, p_bus: String = "") -> void:
var instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, p_position)
instance.trigger_varied(p_pitch, p_volume)
instance.release(true)
## Play a sound event from a SoundBank on a [b]Node2D[/b] or [b]Node3D[/b] with the provided pitch
## and/or volume. This causes the sound to synchronise with the Node's global position - causing
## it to move in 2D or 3D space along with the Node.
func play_on_node_varied(p_bank_label: String, p_event_name: String, p_node, p_pitch: float = 1.0, p_volume: float = 0.0, p_bus: String = "") -> void:
var instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, p_node)
instance.trigger_varied(p_pitch, p_volume)
instance.release(true)
## Returns a reserved [PooledAudioStreamPlayer] for you to use exclusively until it is told to
## [method PooledAudioStreamPlayer.release] or is automatically released when registered
## with [method SoundManager.release_on_exit].
func instance(p_bank_label: String, p_event_name: String, p_bus: String = "") -> Variant:
return _instance_manual(p_bank_label, p_event_name, true, p_bus, false, null)
## Returns a reserved [PooledAudioStreamPlayer2D] or [PooledAudioStreamPlayer3D] (depending on the
## type of [b]p_position[/b]) placed at a specific 2D or 3D position in the world. You will have
## exclusive use of it until it is told to [method PooledAudioStreamPlayer.release] or is automatically
## released when registered with [method SoundManager.release_on_exit].
func instance_at_position(p_bank_label: String, p_event_name: String, p_position, p_bus: String = "") -> Variant:
return _instance_manual(p_bank_label, p_event_name, true, p_bus, false, p_position)
## Returns a reserved [PooledAudioStreamPlayer2D] or [PooledAudioStreamPlayer3D] (depending on the
## type of [b]p_node[/b]) which will synchronise its global position with [b]p_node[/b]. You will have
## exclusive use of it until it is told to [method PooledAudioStreamPlayer.release] or is automatically
## released when registered with [method SoundManager.release_on_exit].
func instance_on_node(p_bank_label: String, p_event_name: String, p_node, p_bus: String = "") -> Variant:
return _instance_manual(p_bank_label, p_event_name, true, p_bus, false, p_node)
## Returns a reserved [PooledAudioStreamPlayer] for you to use exclusively until it is told to
## [method PooledAudioStreamPlayer.release] or is automatically released when registered
## with [method SoundManager.release_on_exit].[br][br]
## [b]Note:[/b] This method will mark the reserved player as polyphonic (able to play
## multiple event variations simultaneously.)
func instance_poly(p_bank_label: String, p_event_name: String, p_bus: String = "") -> Variant:
return _instance_manual(p_bank_label, p_event_name, true, p_bus, true, null)
## Returns a reserved [PooledAudioStreamPlayer2D] or [PooledAudioStreamPlayer3D] (depending on the
## type of [b]p_position[/b]) placed at a specific 2D or 3D position in the world. You will have
## exclusive use of it until it is told to [method PooledAudioStreamPlayer.release] or is automatically
## released when registered with [method SoundManager.release_on_exit].[br][br]
## [b]Note:[/b] This method will mark the reserved player as polyphonic (able to play
## multiple event variations simultaneously.)
func instance_at_position_poly(p_bank_label: String, p_event_name: String, p_position, p_bus: String = "") -> Variant:
return _instance_manual(p_bank_label, p_event_name, true, p_bus, true, p_position)
## Returns a reserved [PooledAudioStreamPlayer2D] or [PooledAudioStreamPlayer3D] (depending on the
## type of [b]p_node[/b]) which will synchronise its global position with [b]p_node[/b]. You will have
## exclusive use of it until it is told to [method PooledAudioStreamPlayer.release] or is automatically
## released when registered with [method SoundManager.release_on_exit].[br][br]
## [b]Note:[/b] This method will mark the reserved player as polyphonic (able to play
## multiple event variations simultaneously.)
func instance_on_node_poly(p_bank_label: String, p_event_name: String, p_node, p_bus: String = "") -> Variant:
return _instance_manual(p_bank_label, p_event_name, true, p_bus, true, p_node)
## Will automatically release the given [b]p_instance[/b] when the provided
## [b]p_base[/b] is removed from the scene tree.
func release_on_exit(p_base: Node, p_instance: Node, p_finish_playing: bool = false) -> void:
if p_instance == null or p_base == null:
return
p_base.tree_exiting.connect(p_instance.release.bind(p_finish_playing))
## Will automatically release the given [b]p_instance[/b] when the provided
## [b]p_base[/b] is removed from the scene tree.[br][br]
## [b]Note:[/b] This method has been deprecated, please use [method SoundManager.release_on_exit] instead.
## @deprecated
func auto_release(p_base: Node, p_instance: Node, p_finish_playing: bool = false) -> Variant:
push_warning("Resonate - auto_release has been deprecated, please use release_on_exit instead.")
if p_instance == null:
return p_instance
release_on_exit(p_base, p_instance, p_finish_playing)
return p_instance
## Manually add a new SoundBank into the event cache.
func add_bank(p_bank: SoundBank) -> void:
_add_bank(p_bank)
## Remove the provided bank from the event cache.
func remove_bank(p_bank_label: String) -> void:
if not _event_table.has(p_bank_label):
return
_event_table.erase(p_bank_label)
## Clear all banks from the event cache.
func clear_banks() -> void:
_event_table.clear()
# ------------------------------------------------------------------------------
# Private methods
# ------------------------------------------------------------------------------
func _on_scene_node_added(p_node: Node) -> void:
if not p_node is SoundBank:
return
_add_bank(p_node)
func _on_scene_node_removed(p_node: Node) -> void:
if not p_node is SoundBank:
return
_remove_bank(p_node)
func _initialise_pool(p_size: int, p_creator_fn: Callable) -> void:
for i in p_size:
p_creator_fn.call_deferred()
func _auto_add_events() -> void:
var sound_banks = ResonateUtils.find_all_nodes(self, "SoundBank")
for sound_bank in sound_banks:
_add_bank(sound_bank)
_event_table_hash = _event_table.hash()
func _add_bank(p_bank: SoundBank) -> void:
if _event_table.has(p_bank.label):
_event_table[p_bank.label]["ref_count"] = \
_event_table[p_bank.label]["ref_count"] + 1
return
_event_table[p_bank.label] = {
"name": p_bank.label,
"bus": p_bank.bus,
"mode": p_bank.mode,
"events": _create_events(p_bank.events),
"ref_count": 1,
}
func _remove_bank(p_bank: SoundBank) -> void:
if not _event_table.has(p_bank.label):
return
if _event_table[p_bank.label]["ref_count"] == 1:
_event_table.erase(p_bank.label)
return
_event_table[p_bank.label]["ref_count"] = \
_event_table[p_bank.label]["ref_count"] - 1
func _create_events(p_events: Array[SoundEventResource]) -> Dictionary:
var events = {}
for event in p_events:
events[event.name] = {
"name": event.name,
"bus": event.bus,
"volume": event.volume,
"pitch": event.pitch,
"streams": event.streams,
}
return events
func _get_bus(p_bank_bus: String, p_event_bus: String) -> String:
if p_event_bus != null and p_event_bus != "":
return p_event_bus
if p_bank_bus != null and p_bank_bus != "":
return p_bank_bus
return ProjectSettings.get_setting(
_settings.SOUND_BANK_BUS_SETTING_NAME,
_settings.SOUND_BANK_BUS_SETTING_DEFAULT)
func _instance_manual(p_bank_label: String, p_event_name: String, p_reserved: bool = false, p_bus: String = "", p_poly: bool = false, p_attachment = null) -> Variant:
if not has_loaded:
push_error("Resonate - The event [%s] on bank [%s] can't be instanced as the SoundManager has not loaded yet. Use the [loaded] signal/event to determine when it is ready." % [p_event_name, p_bank_label])
return _get_null_player(p_attachment)
if not _event_table.has(p_bank_label):
push_error("Resonate - Tried to instance the event [%s] from an unknown bank [%s]." % [p_event_name, p_bank_label])
return _get_null_player(p_attachment)
if not _event_table[p_bank_label]["events"].has(p_event_name):
push_error("Resonate - Tried to instance an unknown event [%s] from the bank [%s]." % [p_event_name, p_bank_label])
return _get_null_player(p_attachment)
var bank = _event_table[p_bank_label] as Dictionary
var event = bank["events"][p_event_name] as Dictionary
if event.streams.size() == 0:
push_error("Resonate - The event [%s] on bank [%s] has no streams, you'll need to add one at minimum." % [p_event_name, p_bank_label])
return _get_null_player(p_attachment)
var player = _get_player(p_attachment)
if player == null:
push_warning("Resonate - The event [%s] on bank [%s] can't be instanced; no pooled players available." % [p_event_name, p_bank_label])
return _get_null_player(p_attachment)
var bus = p_bus if p_bus != "" else _get_bus(bank.bus, event.bus)
player.configure(event.streams, p_reserved, bus, p_poly, event.volume, event.pitch, bank.mode)
player.attach_to(p_attachment)
return player
func _is_player_free(p_player) -> bool:
return not p_player.playing and not p_player.reserved
func _get_player_from_pool(p_pool: Array) -> Variant:
if p_pool.size() == 0:
push_error("Resonate - Player pool has not been initialised. This can occur when calling a [play/instance*] function from [_ready].")
return null
for player in p_pool:
if _is_player_free(player):
return player
push_warning("Resonate - Player pool exhausted, consider increasing the pool size in the project settings (Audio/Manager/Pooling) or releasing unused audio stream players.")
return null
func _get_player_1d() -> PooledAudioStreamPlayer:
return _get_player_from_pool(_1d_players)
func _get_player_2d() -> PooledAudioStreamPlayer2D:
return _get_player_from_pool(_2d_players)
func _get_player_3d() -> PooledAudioStreamPlayer3D:
return _get_player_from_pool(_3d_players)
func _get_player(p_attachment = null) -> Variant:
if ResonateUtils.is_2d_node(p_attachment):
return _get_player_2d()
if ResonateUtils.is_3d_node(p_attachment):
return _get_player_3d()
return _get_player_1d()
func _get_null_player(p_attachment = null) -> Variant:
if ResonateUtils.is_2d_node(p_attachment):
return null_instance_2d()
if ResonateUtils.is_3d_node(p_attachment):
return null_instance_3d()
return null_instance()
func _add_player_to_pool(p_player, p_pool) -> Variant:
add_child(p_player)
p_player.released.connect(_on_player_released.bind(p_player))
p_player.finished.connect(_on_player_finished.bind(p_player))
p_pool.append(p_player)
return p_player
func _create_player_1d() -> PooledAudioStreamPlayer:
return _add_player_to_pool(PooledAudioStreamPlayer.create(), _1d_players)
func _create_player_2d() -> PooledAudioStreamPlayer2D:
return _add_player_to_pool(PooledAudioStreamPlayer2D.create(), _2d_players)
func _create_player_3d() -> PooledAudioStreamPlayer3D:
return _add_player_to_pool(PooledAudioStreamPlayer3D.create(), _3d_players)
func _on_player_released(p_player: Node) -> void:
if p_player.playing:
return
pools_updated.emit()
updated.emit()
func _on_player_finished(p_player: Node) -> void:
if p_player.reserved:
return
pools_updated.emit()
updated.emit()