Compare commits
3
Commits
bd0629f554
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bcc12b45b | ||
|
|
5204295864 | ||
|
|
1e53fb83d1 |
@@ -90,17 +90,17 @@ When the node enters the tree in the editor, `stream` is auto-assigned an `Audio
|
|||||||
| `pause()` | Pauses playback. No-op if not playing or already paused. |
|
| `pause()` | Pauses playback. No-op if not playing or already paused. |
|
||||||
| `resume()` | Resumes playback after a pause. |
|
| `resume()` | Resumes playback after a pause. |
|
||||||
| `is_song_playing() -> bool` | True iff a song's layers are active and not paused. Distinct from `is_playing()`, which stays true between songs because the polyphonic playback session itself is still live. |
|
| `is_song_playing() -> bool` | True iff a song's layers are active and not paused. Distinct from `is_playing()`, which stays true between songs because the polyphonic playback session itself is still live. |
|
||||||
| `fade_in(duration: float)` | Starts playback (if needed), unpauses, and ramps volume from silent to `volume_db` over `duration` seconds. Works regardless of `autoplay`. |
|
| `fade_in(duration: float)` | Animates `volume_db` up to the user's set level over `duration` seconds. **Pure volume operation** — does not start playback, unpause, or otherwise change playing state. The "set level" is the last value written to `volume_db` outside of an active fade, so `fade_in` → `fade_out` → `fade_in` round-trips back to the original volume. If any fade is in flight (player-wide `fade_out` or per-song crossfade from `SkipMode = CrossFade`), the ramp continues from the current `volume_db` so audible state is preserved. Otherwise it snaps to silent and ramps up. **Idempotent** — calling it while already fading to the same target is a no-op (won't restart the fade clock). Pair with `play_playlist()` / `resume()` if the player isn't already playing. |
|
||||||
| `fade_out(duration: float)` | Ramps volume from current level to silent over `duration` seconds, then stops the polyphonic session. A subsequent `fade_in` reinitialises. |
|
| `fade_out(duration: float)` | Animates `volume_db` from its current value down to silent over `duration` seconds. **Pure volume operation** — does not stop the polyphonic session or pause. Call `pause()` / `stop()` yourself afterwards if you want playback to halt. **Idempotent** — calling it while already fading to silent is a no-op. |
|
||||||
| `get_current_song_index() -> int` | Index of the song most recently kicked off in `Playlist.QueuedSongs`. During a reverb-tail crossover this becomes the incoming song the moment it starts, even though the outgoing song is still audible. |
|
| `get_current_song_index() -> int` | Index of the song most recently kicked off in `Playlist.QueuedSongs`. During a reverb-tail crossover this becomes the incoming song the moment it starts, even though the outgoing song is still audible. |
|
||||||
| `advance_to_next_song()` | Skip to the next song in the playlist (wraps from the end back to index 0). Transition style is controlled by `SkipMode`. |
|
| `advance_to_next_song()` | Skip to the next song in the playlist (wraps from the end back to index 0). Transition style is controlled by `SkipMode`. |
|
||||||
| `advance_to_track(index: int)` | Same as `advance_to_next_song`, but jumps to a specific index. Out-of-range indices are ignored. |
|
| `advance_to_track(index: int)` | Same as `advance_to_next_song`, but jumps to a specific index. Out-of-range indices are ignored. **No-op when called with the index of the song already playing** — won't restart it. |
|
||||||
|
|
||||||
#### Behaviour
|
#### Behaviour
|
||||||
|
|
||||||
- **Autoplay.** On `_ready`, plays the song at the current index *only if `autoplay` is true* (the inherited `AudioStreamPlayer.autoplay` property; default `true`). Set `autoplay = false` to defer playback to a `play_playlist()` or `fade_in()` call.
|
- **Autoplay.** On `_ready`, plays the song at the current index *only if `autoplay` is true* (the inherited `AudioStreamPlayer.autoplay` property; default `true`). Set `autoplay = false` to defer playback to a `play_playlist()` or `fade_in()` call.
|
||||||
- **Reverb-tail transitions.** When the current song is within `ReverbTail` seconds of its full length, the next song (chosen per `LoopMode`) is started so its leading audio covers the outgoing tail. Both songs are audible during the overlap window; the outgoing one stops when its full length has elapsed.
|
- **Reverb-tail transitions.** When the current song is within `ReverbTail` seconds of its full length, the next song (chosen per `LoopMode`) is started so its leading audio covers the outgoing tail. Both songs are audible during the overlap window; the outgoing one stops when its full length has elapsed.
|
||||||
- **Volume.** `volume_db` is honoured; the fade adjustment is layered on top internally, so manual volume changes during a fade still work correctly. During a reverb-tail overlap, both songs share the same volume/intensity mix.
|
- **Volume.** `fade_in` and `fade_out` animate `volume_db` directly — the property visibly changes each frame during a fade. Setting `volume_db` mid-fade will be overwritten on the next frame; let the fade complete (or stop it by triggering another fade) before nudging the property manually. During a reverb-tail overlap, both songs share the same volume/intensity mix.
|
||||||
- **Editor.** All scripts are `@tool`, but `_ready` and `_process` early-return in the editor so audio doesn't play in the inspector.
|
- **Editor.** All scripts are `@tool`, but `_ready` and `_process` early-return in the editor so audio doesn't play in the inspector.
|
||||||
|
|
||||||
### `DynamicSoundFXPlayer` family — overlapping one-shot SFX
|
### `DynamicSoundFXPlayer` family — overlapping one-shot SFX
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ var _core : DynamicSoundPlayerCore;
|
|||||||
|
|
||||||
func _set(property: StringName, value: Variant) -> bool:
|
func _set(property: StringName, value: Variant) -> bool:
|
||||||
if property == &"volume_db":
|
if property == &"volume_db":
|
||||||
if _core: _core.apply_stream_volumes(value);
|
if _core: _core.on_volume_db_set(value);
|
||||||
return false;
|
return false;
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ var _core : DynamicSoundPlayerCore;
|
|||||||
|
|
||||||
func _set(property: StringName, value: Variant) -> bool:
|
func _set(property: StringName, value: Variant) -> bool:
|
||||||
if property == &"volume_db":
|
if property == &"volume_db":
|
||||||
if _core: _core.apply_stream_volumes(value);
|
if _core: _core.on_volume_db_set(value);
|
||||||
return false;
|
return false;
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ var _core : DynamicSoundPlayerCore;
|
|||||||
|
|
||||||
func _set(property: StringName, value: Variant) -> bool:
|
func _set(property: StringName, value: Variant) -> bool:
|
||||||
if property == &"volume_db":
|
if property == &"volume_db":
|
||||||
if _core: _core.apply_stream_volumes(value);
|
if _core: _core.on_volume_db_set(value);
|
||||||
return false;
|
return false;
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -41,16 +41,18 @@ var _playback : AudioStreamPlaybackPolyphonic;
|
|||||||
var _cur_time : float = 0.0;
|
var _cur_time : float = 0.0;
|
||||||
var _current_index : int = 0;
|
var _current_index : int = 0;
|
||||||
var _paused : bool = false;
|
var _paused : bool = false;
|
||||||
var _fade_db : float = 0.0;
|
|
||||||
var _fade_from_db : float = 0.0;
|
var _fade_from_db : float = 0.0;
|
||||||
var _fade_to_db : float = 0.0;
|
var _fade_to_db : float = 0.0;
|
||||||
var _fade_duration : float = 0.0;
|
var _fade_duration : float = 0.0;
|
||||||
var _fade_elapsed : float = 0.0;
|
var _fade_elapsed : float = 0.0;
|
||||||
var _fading : bool = false;
|
var _fading : bool = false;
|
||||||
var _stop_when_faded : bool = false;
|
# Stable record of the user's set volume. Fades animate volume_db directly,
|
||||||
|
# so without this we lose the original target across fade interruptions.
|
||||||
|
var _target_volume_db : float = 0.0;
|
||||||
|
|
||||||
func _init(player) -> void:
|
func _init(player) -> void:
|
||||||
_player = player;
|
_player = player;
|
||||||
|
_target_volume_db = player.volume_db;
|
||||||
|
|
||||||
func ready() -> void:
|
func ready() -> void:
|
||||||
if Engine.is_editor_hint():
|
if Engine.is_editor_hint():
|
||||||
@@ -68,13 +70,9 @@ func process(delta: float) -> void:
|
|||||||
if _fading:
|
if _fading:
|
||||||
_fade_elapsed += delta;
|
_fade_elapsed += delta;
|
||||||
var t : float = clampf(_fade_elapsed / _fade_duration, 0.0, 1.0);
|
var t : float = clampf(_fade_elapsed / _fade_duration, 0.0, 1.0);
|
||||||
_fade_db = lerp(_fade_from_db, _fade_to_db, t);
|
_player.volume_db = lerp(_fade_from_db, _fade_to_db, t);
|
||||||
apply_stream_volumes(_player.volume_db);
|
|
||||||
if t >= 1.0:
|
if t >= 1.0:
|
||||||
_fading = false;
|
_fading = false;
|
||||||
if _stop_when_faded:
|
|
||||||
_stop_after_fade();
|
|
||||||
return;
|
|
||||||
# iterate over a snapshot since we may modify _active_songs in the loop
|
# iterate over a snapshot since we may modify _active_songs in the loop
|
||||||
for active in _active_songs.duplicate():
|
for active in _active_songs.duplicate():
|
||||||
# advance per-song fade
|
# advance per-song fade
|
||||||
@@ -144,6 +142,8 @@ func advance_to_track(index: int) -> void:
|
|||||||
var playlist : DynamicSoundPlaylist = _player.Playlist;
|
var playlist : DynamicSoundPlaylist = _player.Playlist;
|
||||||
if playlist == null or index < 0 or index >= playlist.QueuedSongs.size():
|
if playlist == null or index < 0 or index >= playlist.QueuedSongs.size():
|
||||||
return;
|
return;
|
||||||
|
if index == _current_index and not _active_songs.is_empty():
|
||||||
|
return;
|
||||||
if _playback == null:
|
if _playback == null:
|
||||||
_player.play();
|
_player.play();
|
||||||
_playback = _player.get_stream_playback();
|
_playback = _player.get_stream_playback();
|
||||||
@@ -170,18 +170,36 @@ func advance_to_track(index: int) -> void:
|
|||||||
_start_song_fade(incoming, _FADE_SILENT_DB, 0.0, _player.CrossFadeDuration, false);
|
_start_song_fade(incoming, _FADE_SILENT_DB, 0.0, _player.CrossFadeDuration, false);
|
||||||
_apply_volumes_to_song(incoming, _player.volume_db);
|
_apply_volumes_to_song(incoming, _player.volume_db);
|
||||||
|
|
||||||
|
## Ramps [code]volume_db[/code] up to the user's set level over [param duration]
|
||||||
|
## seconds. Pure volume operation — does not call [code]play()[/code], unpause,
|
||||||
|
## or change playing state. If any fade is in flight (player-wide or per-song
|
||||||
|
## crossfade), the ramp continues from the current [code]volume_db[/code] —
|
||||||
|
## no snap to silent, so audible crossfade state is preserved. Otherwise the
|
||||||
|
## ramp snaps to silent first and rises to the user's set level.
|
||||||
|
## Idempotent: no-op if a fade is already heading to this same target.
|
||||||
func fade_in(duration: float) -> void:
|
func fade_in(duration: float) -> void:
|
||||||
_start_playback();
|
var target_db : float = _target_volume_db;
|
||||||
if _paused:
|
if target_db <= _FADE_SILENT_DB + 0.001:
|
||||||
resume();
|
target_db = 0.0;
|
||||||
_start_fade(_FADE_SILENT_DB, 0.0, duration, false);
|
if _fading and is_equal_approx(_fade_to_db, target_db):
|
||||||
|
|
||||||
func fade_out(duration: float) -> void:
|
|
||||||
if _playback == null:
|
|
||||||
return;
|
return;
|
||||||
if _paused:
|
var any_song_fading : bool = false;
|
||||||
resume();
|
for active in _active_songs:
|
||||||
_start_fade(_fade_db, _FADE_SILENT_DB, duration, true);
|
if active.fading:
|
||||||
|
any_song_fading = true;
|
||||||
|
break;
|
||||||
|
var from_db : float = _player.volume_db if (_fading or any_song_fading) else _FADE_SILENT_DB;
|
||||||
|
_start_fade(from_db, target_db, duration);
|
||||||
|
|
||||||
|
## Ramps [code]volume_db[/code] from its current value down to silent over
|
||||||
|
## [param duration] seconds. Pure volume operation — does not stop the
|
||||||
|
## polyphonic session or pause. Call [method pause] or [code]stop()[/code]
|
||||||
|
## yourself if you want playback to halt at the end.
|
||||||
|
## Idempotent: no-op if a fade is already heading to silent.
|
||||||
|
func fade_out(duration: float) -> void:
|
||||||
|
if _fading and is_equal_approx(_fade_to_db, _FADE_SILENT_DB):
|
||||||
|
return;
|
||||||
|
_start_fade(_player.volume_db, _FADE_SILENT_DB, duration);
|
||||||
|
|
||||||
func apply_stream_volumes(value: float) -> void:
|
func apply_stream_volumes(value: float) -> void:
|
||||||
if _playback == null:
|
if _playback == null:
|
||||||
@@ -189,6 +207,14 @@ func apply_stream_volumes(value: float) -> void:
|
|||||||
for active in _active_songs:
|
for active in _active_songs:
|
||||||
_apply_volumes_to_song(active, value);
|
_apply_volumes_to_song(active, value);
|
||||||
|
|
||||||
|
## Wrapper-side hook for [code]volume_db[/code] writes. Updates [code]_target_volume_db[/code]
|
||||||
|
## (the user's set level) only when no fade is animating it, so transient lerp
|
||||||
|
## values aren't captured as a new target.
|
||||||
|
func on_volume_db_set(value: float) -> void:
|
||||||
|
if not _fading:
|
||||||
|
_target_volume_db = value;
|
||||||
|
apply_stream_volumes(value);
|
||||||
|
|
||||||
func _start_playback() -> void:
|
func _start_playback() -> void:
|
||||||
var playlist : DynamicSoundPlaylist = _player.Playlist;
|
var playlist : DynamicSoundPlaylist = _player.Playlist;
|
||||||
if playlist == null or playlist.QueuedSongs.is_empty():
|
if playlist == null or playlist.QueuedSongs.is_empty():
|
||||||
@@ -237,7 +263,7 @@ func _stop_active_song(active: _ActiveSong) -> void:
|
|||||||
_playback.stop_stream(id);
|
_playback.stop_stream(id);
|
||||||
|
|
||||||
func _apply_volumes_to_song(active: _ActiveSong, volume_db_value: float) -> void:
|
func _apply_volumes_to_song(active: _ActiveSong, volume_db_value: float) -> void:
|
||||||
var effective_db : float = volume_db_value + _fade_db + active.fade_db;
|
var effective_db : float = volume_db_value + active.fade_db;
|
||||||
var realIntensity : float;
|
var realIntensity : float;
|
||||||
if active.ids.size() == 3:
|
if active.ids.size() == 3:
|
||||||
realIntensity = _player.Intensity;
|
realIntensity = _player.Intensity;
|
||||||
@@ -247,15 +273,13 @@ func _apply_volumes_to_song(active: _ActiveSong, volume_db_value: float) -> void
|
|||||||
# set volume based on intensity; with a single layer it's just full volume
|
# set volume based on intensity; with a single layer it's just full volume
|
||||||
_playback.set_stream_volume(active.ids[i], linear_to_db(db_to_linear(effective_db) * max((.5 - abs(float(i)/2 - realIntensity))/.5, 0)));
|
_playback.set_stream_volume(active.ids[i], linear_to_db(db_to_linear(effective_db) * max((.5 - abs(float(i)/2 - realIntensity))/.5, 0)));
|
||||||
|
|
||||||
func _start_fade(from_db: float, to_db: float, duration: float, stop_after: bool) -> void:
|
func _start_fade(from_db: float, to_db: float, duration: float) -> void:
|
||||||
_fade_from_db = from_db;
|
_fade_from_db = from_db;
|
||||||
_fade_to_db = to_db;
|
_fade_to_db = to_db;
|
||||||
_fade_db = from_db;
|
|
||||||
_fade_duration = max(duration, 0.0001);
|
_fade_duration = max(duration, 0.0001);
|
||||||
_fade_elapsed = 0.0;
|
_fade_elapsed = 0.0;
|
||||||
_fading = true;
|
_fading = true;
|
||||||
_stop_when_faded = stop_after;
|
_player.volume_db = from_db;
|
||||||
apply_stream_volumes(_player.volume_db);
|
|
||||||
|
|
||||||
func _start_song_fade(song: _ActiveSong, from_db: float, to_db: float, duration: float, stop_after: bool) -> void:
|
func _start_song_fade(song: _ActiveSong, from_db: float, to_db: float, duration: float, stop_after: bool) -> void:
|
||||||
song.fade_from_db = from_db;
|
song.fade_from_db = from_db;
|
||||||
@@ -265,12 +289,3 @@ func _start_song_fade(song: _ActiveSong, from_db: float, to_db: float, duration:
|
|||||||
song.fade_elapsed = 0.0;
|
song.fade_elapsed = 0.0;
|
||||||
song.fading = true;
|
song.fading = true;
|
||||||
song.stop_after_fade = stop_after;
|
song.stop_after_fade = stop_after;
|
||||||
|
|
||||||
func _stop_after_fade() -> void:
|
|
||||||
for active in _active_songs:
|
|
||||||
_stop_active_song(active);
|
|
||||||
_active_songs.clear();
|
|
||||||
_player.stop();
|
|
||||||
_playback = null;
|
|
||||||
_fade_db = 0.0;
|
|
||||||
_stop_when_faded = false;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user