Fades are now pure volume ramps: they no longer call play(), stop(), or unpause. The animation is visible on the volume_db property each frame. Track _target_volume_db separately so fade_in/fade_out round-trips return to the user's set level instead of a transient lerp value, and skip the "snap to silent" when interrupting an in-flight player or per-song crossfade so audible state is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
98 lines
3.5 KiB
GDScript
98 lines
3.5 KiB
GDScript
@tool
|
|
@icon("res://addons/gd-dynamic-sound/gd-dynamic-sound-icon.jpg")
|
|
class_name DynamicSoundPlayer3D
|
|
## 3D positional variant of [DynamicSoundPlayer].
|
|
##
|
|
## Same playlist, intensity, fade, and pause behaviour — fully spatialised with
|
|
## distance attenuation, directionality, and Doppler.
|
|
extends AudioStreamPlayer3D
|
|
|
|
|
|
## Songs to play in order. Mutated as songs finish: each finished song is
|
|
## popped, and re-appended at the back when [member Loop] is on.
|
|
@export var Playlist : DynamicSoundPlaylist;
|
|
|
|
## Runtime mix between the three intensity layers. [code]0[/code] = least intense, [code]1[/code] = most intense.
|
|
@export_range(0, 1) var Intensity : float = 0:
|
|
set(value):
|
|
Intensity = value;
|
|
if _core: _core.apply_stream_volumes(volume_db);
|
|
|
|
## How the playlist loops when the current song ends.
|
|
## [code]Single[/code] keeps playing the same song; [code]All[/code] advances
|
|
## through the playlist and wraps from the last song back to the first.
|
|
@export var LoopMode : DynamicSoundConstants.LoopMode = DynamicSoundConstants.LoopMode.All;
|
|
|
|
## How [method advance_to_next_song] and [method advance_to_track] transition between songs.
|
|
## Does not affect the natural end-of-song advance, which always uses the song's reverb tail.
|
|
@export var SkipMode : DynamicSoundConstants.SkipMode = DynamicSoundConstants.SkipMode.UseReverbTail:
|
|
set(value):
|
|
SkipMode = value;
|
|
notify_property_list_changed();
|
|
|
|
## Crossfade duration in seconds when [member SkipMode] is [code]CrossFade[/code].
|
|
@export var CrossFadeDuration : float = 1.0;
|
|
|
|
var _core : DynamicSoundPlayerCore;
|
|
|
|
func _set(property: StringName, value: Variant) -> bool:
|
|
if property == &"volume_db":
|
|
if _core: _core.on_volume_db_set(value);
|
|
return false;
|
|
return false;
|
|
|
|
func _enter_tree() -> void:
|
|
if not Engine.is_editor_hint():
|
|
return;
|
|
if stream == null:
|
|
stream = AudioStreamPolyphonic.new();
|
|
if bus == &"Master" and AudioServer.get_bus_index("Music") != -1:
|
|
bus = "Music";
|
|
|
|
func _validate_property(property: Dictionary) -> void:
|
|
if property.name == "CrossFadeDuration" and SkipMode != DynamicSoundConstants.SkipMode.CrossFade:
|
|
property.usage &= ~PROPERTY_USAGE_EDITOR;
|
|
|
|
func _ready() -> void:
|
|
_core = DynamicSoundPlayerCore.new(self);
|
|
_core.ready();
|
|
|
|
func _process(delta: float) -> void:
|
|
if _core: _core.process(delta);
|
|
|
|
## Start (or resume) playlist playback. Useful when [code]autoplay[/code] is false.
|
|
func play_playlist() -> void:
|
|
if _core: _core.play_playlist();
|
|
|
|
## Index of the song most recently started in the playlist.
|
|
func get_current_song_index() -> int:
|
|
return _core.get_current_song_index() if _core else 0;
|
|
|
|
## Skip to the next song (wraps to index 0). Smooth via the current song's reverb tail.
|
|
func advance_to_next_song() -> void:
|
|
if _core: _core.advance_to_next_song();
|
|
|
|
## Jump to the song at [param index]. Smooth via the current song's reverb tail.
|
|
func advance_to_track(index: int) -> void:
|
|
if _core: _core.advance_to_track(index);
|
|
|
|
## Pause playback. Call [method resume] to continue.
|
|
func pause() -> void:
|
|
if _core: _core.pause();
|
|
|
|
## Resume playback after a [method pause].
|
|
func resume() -> void:
|
|
if _core: _core.resume();
|
|
|
|
## True if a song is currently playing (and not paused).
|
|
func is_song_playing() -> bool:
|
|
return _core != null and _core.is_song_playing();
|
|
|
|
## Start playback (if needed) and fade volume up from silent over [param duration] seconds.
|
|
func fade_in(duration: float) -> void:
|
|
if _core: _core.fade_in(duration);
|
|
|
|
## Fade volume to silent over [param duration] seconds, then stop playback.
|
|
func fade_out(duration: float) -> void:
|
|
if _core: _core.fade_out(duration);
|