advance_to_next_song / advance_to_track now branch on the new SkipMode export: - UseReverbTail (default): existing behavior. Accelerate the outgoing song so remaining time hits ReverbTail, both songs play during the overlap, outgoing stops naturally. - CrossFade: programmatic volume crossfade. Outgoing fades from current volume to silent over CrossFadeDuration seconds (then stops), incoming starts silent and fades up to full over the same duration. Independent of any global fade. - Jump: hard cut. Outgoing song's streams are stopped immediately and removed from the active list before the incoming starts. For CrossFade we needed per-song fade state (the global _fade_db can't drive two songs in opposite directions at once), so _ActiveSong gained fade_db / fade_from_db / fade_to_db / fade_duration / fade_elapsed / fading / stop_after_fade fields. _apply_volumes_to_song now adds both the global and per-song fade_db to volume_db; process() advances each song's fade per-frame and stops songs whose fades reach the silent target with stop_after_fade set. CrossFadeDuration is hidden in the inspector via _validate_property unless SkipMode == CrossFade. The natural end-of-song advance is unchanged — it still uses the song's reverb tail. SkipMode only affects programmatic skips. 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/icon.svg")
|
|
class_name DynamicSoundPlayer2D
|
|
## 2D positional variant of [DynamicSoundPlayer].
|
|
##
|
|
## Same playlist, intensity, fade, and pause behaviour — distance-attenuated
|
|
## from a 2D position relative to the listener.
|
|
extends AudioStreamPlayer2D
|
|
|
|
|
|
## 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.apply_stream_volumes(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);
|