diff --git a/audio/AudioFader.gd b/audio/AudioFader.gd new file mode 100644 index 0000000..da7df08 --- /dev/null +++ b/audio/AudioFader.gd @@ -0,0 +1,73 @@ +class_name AudioFader extends Node +## Controls an AudioStreamPlayer and allows for fading and crossfading. + +## Audio fade-out target. +const AUDIO_FADE_MIN_VOLUME: float = -20.0 +## Default audio transition time. +const DEFAULT_AUDIO_FADE: float = 1.0 + +## Audio bus. +@export var bus: StringName + +## Current AudioStreamPlayer. +var player: AudioStreamPlayer = null +## Current TweenChannel. +var tween_channel: TweenChannel = null + +## Creates an initial AudioStreamPlayer child and TweenChannel. +func _ready() -> void: + _create_members() + +## Creates an AudioStreamPlayer child and TweenChannel. +func _create_members() -> void: + player = AudioStreamPlayer.new() + add_child(player) + tween_channel = TweenChannel.make_replacing() + +## Fades in the current audio player. +func fade_in(stream: AudioStream, fade: float = DEFAULT_AUDIO_FADE) -> void: + await fade_out() + player.volume_db = AUDIO_FADE_MIN_VOLUME + player.stream = stream + player.play() + var tween := await tween_channel.create_tween(player) + tween.tween_property(player, ^'volume_db', 0.0, fade) + await tween_channel.sync(tween) + +## Fades out an arbitrary audio player on an arbitrary tween channel. +static func _fade_out_foreign( + p_player: AudioStreamPlayer, + p_tween_channel: TweenChannel, + fade: float = DEFAULT_AUDIO_FADE +) -> void: + if p_player.volume_db > AUDIO_FADE_MIN_VOLUME: + var tween := await p_tween_channel.create_tween(p_player) + tween.tween_property( + p_player, ^'volume_db', AUDIO_FADE_MIN_VOLUME, fade + ) + await p_tween_channel.sync(tween) + p_player.stop() + +## Fades out the current audio player. +func fade_out(fade: float = DEFAULT_AUDIO_FADE) -> void: + await AudioFader._fade_out_foreign(player, tween_channel, fade) + +## Plays audio without fading in. +func play(stream: AudioStream) -> void: + await player.fade_out() + player.volume_db = 0.0 + player.stream = stream + player.play() + +## Fades out old audio and fades in new audio at the same time. +func crossfade(stream: AudioStream, fade: float = DEFAULT_AUDIO_FADE) -> void: + var old_player := player + var old_tween_channel := tween_channel + var thunk := func() -> void: + await AudioFader._fade_out_foreign(old_player, old_tween_channel, fade) + old_player.queue_free() + _create_members() + await Wait.until(player.is_node_ready) + var task := Task.new(thunk) + await fade_in(stream, fade) + await task.sync()