Last active
July 27, 2023 07:16
-
-
Save lemilonkh/c03715e67cb3b1e14875a851eb978a58 to your computer and use it in GitHub Desktop.
Adaptive audio players (made for Godot 3.5 during Godot Wild Jam 49)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node2D | |
# Meant to be used as an autoload singleton | |
const player_count = 32 | |
const bus = "Effects" | |
const sound_folder = "res://sounds/" | |
const queue_limit = 64 | |
var available_players = [] | |
var sound_queue = [] | |
var data_queue = [] | |
var loaded_sounds = {} # sound name => loaded resource | |
var file = File.new() | |
var listener: Listener2D | |
func _ready() -> void: | |
pause_mode = PAUSE_MODE_PROCESS | |
for _i in range(player_count): | |
var player = AudioStreamPlayer.new() | |
add_child(player) | |
available_players.append(player) | |
player.connect("finished", self, "_on_stream_finished", [player]) | |
player.bus = bus | |
func _on_stream_finished(player: AudioStreamPlayer) -> void: | |
available_players.append(player) | |
func play(sound_name: String, position: Vector2 = Vector2.INF, volume: float = -0.5) -> void: | |
# discard sounds if there are too many queued | |
if sound_queue.size() >= queue_limit: | |
printerr("AudioManager: Sound queue is full, increase players or play less sounds!") | |
return | |
sound_queue.append(sound_name) | |
data_queue.append({ "position": position, "volume": volume }) | |
func _process(delta: float) -> void: | |
if not sound_queue.empty() and not available_players.empty(): | |
var player: AudioStreamPlayer = available_players.pop_front() | |
var sound_name: String = sound_queue.pop_front() | |
if not loaded_sounds.has(sound_name): | |
var path = sound_folder + sound_name + ".wav" | |
if not file.file_exists(path): | |
printerr("AudioManager: Missing sound ", path) | |
return | |
loaded_sounds[sound_name] = load(path) | |
player.stream = loaded_sounds[sound_name] | |
var data = data_queue.pop_front() | |
player.volume_db = data.volume | |
# if data.position == Vector2.INF: | |
# player.position = listener.position | |
# player.attenuation = 0 | |
# else: | |
# player.position = data.position | |
# player.attenuation = 1 | |
player.play() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Song | |
const STATE_FADE_BARS = 1 | |
onready var daytime_section_tracks = { | |
0: $MorningExploration, | |
1: $DaytimeExploration, | |
2: $EveningExploration, | |
# TODO 3: "Night", ? | |
} | |
onready var daytime_nocombat_tracks = { | |
0: $MorningNoCombatExploration, | |
1: $DaytimeNoCombatExploration, | |
2: $EveningNoCombatExploration, | |
} | |
onready var combat_track: AudioStreamPlayer = $CombatExploration | |
onready var tension_track: AudioStreamPlayer = $TensionExploration | |
onready var perc_track: AudioStreamPlayer = $PercExploration | |
var current_daytime_section = null | |
var previous_daytime_section = null | |
var main_volume = 0.0 | |
var combat_fade = 0.0 # [0,1] | |
var target_combat_fade = 0.0 # either 0.0 or 1.0 | |
var is_combat_fade_active = false | |
var tension_fade = 0.0 # [0,1] | |
func _ready() -> void: | |
for track in get_children(): | |
track.volume_db = Music.SILENT_VOLUME | |
main_volume = Music.SILENT_VOLUME | |
func _process(delta: float) -> void: | |
if is_playing and current_daytime_section != null: | |
var current_nocombat_track = _get_track(current_daytime_section, "nocombat") | |
var current_section_track = _get_track(current_daytime_section) | |
var previous_nocombat_track = _get_track(previous_daytime_section, "nocombat") | |
var previous_section_track = _get_track(previous_daytime_section) | |
tension_track.volume_db = _fade_volume(main_volume, tension_fade) | |
perc_track.volume_db = _fade_volume(main_volume, tension_fade) | |
combat_track.volume_db = _fade_volume(main_volume, combat_fade) | |
current_nocombat_track.volume_db = _fade_volume(current_section_track.volume_db, 1.0 - combat_fade) | |
previous_nocombat_track.volume_db = _fade_volume(previous_section_track.volume_db, 1.0 - combat_fade) | |
func fade_volume(target_volume: float, track: AudioStreamPlayer = null, duration: float = default_fade_duration) -> void: | |
var tween = create_tween().set_parallel().set_trans(Tween.TRANS_QUAD) | |
if track != null: | |
tween.tween_property(track, "volume_db", target_volume, duration) | |
elif target_volume <= Music.SILENT_VOLUME: | |
for track in get_children(): | |
tween.tween_property(track, "volume_db", target_volume, duration) | |
tween.tween_property(self, "main_volume", target_volume, duration) | |
# TODO if play() is called in before this tween is finished, we should not pause | |
yield(tween, "finished") | |
pause() | |
else: | |
var current_section_track = _get_track(current_daytime_section) | |
tween.tween_property(current_section_track, "volume_db", target_volume, duration) | |
tween.tween_property(self, "main_volume", target_volume, duration) | |
func play() -> void: | |
is_playing = true | |
for track in get_children(): | |
track.play() | |
func pause() -> void: | |
is_playing = false | |
for track in get_children(): | |
track.stop() | |
func set_property(property_name: String, value: float) -> void: | |
if property_name == "daytime_section": | |
set_daytime_section(int(value)) | |
elif property_name == "enemy_distance": | |
# fade Tension track with distance [0,1] | |
var tween = create_tween().set_trans(Tween.TRANS_QUAD) | |
tween.tween_property(self, "tension_fade", value, 0.1) | |
elif property_name == "is_combat" and target_combat_fade != value and not is_combat_fade_active: | |
var tween = create_tween().set_trans(Tween.TRANS_LINEAR) | |
var duration = _bars_to_seconds(STATE_FADE_BARS) | |
if not value: | |
duration *= 4 | |
tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT) | |
is_combat_fade_active = true | |
target_combat_fade = value | |
tween.tween_property(self, "combat_fade", value, duration) | |
yield(tween, "finished") | |
is_combat_fade_active = false | |
func set_daytime_section(section: int) -> void: | |
if current_daytime_section == section: | |
print("Already playing daytime section ", section) | |
return | |
if not is_inside_tree(): | |
yield(self, "ready") | |
current_daytime_section = section | |
if not daytime_section_tracks.has(section): | |
printerr("Music track for daytime section %d doesn't exist!" % section) | |
return | |
var track = daytime_section_tracks[section] | |
print("Fading in track ", track.name) | |
fade_volume(Music.FULL_VOLUME, track) | |
if previous_daytime_section != null: | |
if not daytime_section_tracks.has(previous_daytime_section): | |
printerr("Music track for previous daytime section %d doesn't exist!" % previous_daytime_section) | |
return | |
var previous_track: AudioStreamPlayer = daytime_section_tracks[previous_daytime_section] | |
print("Fading out track ", previous_track.name) | |
fade_volume(Music.SILENT_VOLUME, previous_track) | |
previous_daytime_section = current_daytime_section | |
func _get_track(section: int = current_daytime_section, type: String = "") -> AudioStreamPlayer: | |
var track_list: Dictionary = daytime_section_tracks | |
if type.to_lower() == "nocombat": | |
track_list = daytime_nocombat_tracks | |
if not track_list.has(section): | |
printerr("Music track for daytime section %d doesn't exist!" % section) | |
return null | |
return track_list[section] | |
# apply fade [0,1] to volume [-60,0] | |
func _fade_volume(volume: float, fade: float) -> float: | |
var volume_progress = range_lerp(volume, Music.SILENT_VOLUME, Music.FULL_VOLUME, 0, 1) | |
var faded_progress = volume_progress * fade | |
return range_lerp(faded_progress, 0, 1, Music.SILENT_VOLUME, Music.FULL_VOLUME) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Song | |
export(int) var loop_fade_bars = 17 # fade track into itself after X bars | |
var copy_track: AudioStreamPlayer | |
var loop_fade_seconds | |
func _ready() -> void: | |
loop_fade_seconds = _bars_to_seconds(loop_fade_bars) | |
copy_track = default_track.duplicate() | |
copy_track.volume_db = Music.SILENT_VOLUME | |
add_child(copy_track) | |
func _process(delta: float) -> void: | |
if not is_playing: | |
return | |
var current_position = default_track.get_playback_position() | |
if current_position >= loop_fade_seconds: | |
fade_volume(Music.SILENT_VOLUME, default_track, default_fade_duration, false) | |
fade_volume(Music.FULL_VOLUME, copy_track, default_fade_duration, false) | |
copy_track.play() | |
var swap_track = default_track | |
default_track = copy_track | |
copy_track = swap_track | |
func fade_volume( | |
target_volume: float, | |
track: AudioStreamPlayer = default_track, | |
duration: float = default_fade_duration, | |
should_stop: bool = true | |
) -> void: | |
var tween = create_tween().set_parallel().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) | |
tween.tween_property(track, "volume_db", target_volume, duration) | |
if target_volume == Music.SILENT_VOLUME and should_stop: | |
yield(tween, "finished") | |
pause() | |
func play() -> void: | |
is_playing = true | |
default_track.play() | |
func pause() -> void: | |
is_playing = false | |
default_track.stop() | |
copy_track.stop() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node | |
# Add this as an autoload singleton node with the name "Music" | |
const SILENT_VOLUME = -64.0 # dB | |
const FULL_VOLUME = -0.5 # dB | |
export(String) var autoplay_song | |
var current_song: Node | |
func _ready() -> void: | |
if autoplay_song: | |
play(autoplay_song) | |
func switch_song(song_name: String) -> void: | |
if current_song and current_song.get_name() == song_name: | |
print("Song already playing: ", song_name) | |
return | |
# TODO cache song nodes in dictionary | |
var next_song = get_node_or_null(song_name) | |
if not next_song: | |
printerr("Can't switch - song missing: ", song_name) | |
return | |
if current_song: | |
current_song.fade_volume(SILENT_VOLUME) | |
next_song.play() | |
next_song.fade_volume(FULL_VOLUME) | |
current_song = next_song | |
func set_song_property(song_name: String, property_name: String, value: float) -> void: | |
var song = get_node_or_null(song_name) | |
if not song: | |
printerr("Can't set property - song missing: ", song_name) | |
return | |
song.set_property(property_name, value) | |
func play(song_name: String = ""): | |
if song_name: | |
current_song = get_node_or_null(song_name) | |
if not current_song: | |
printerr("Can't play - no song passed or selected! ", song_name) | |
return | |
current_song.play() | |
func pause(): | |
if current_song: | |
current_song.pause() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node | |
class_name Song | |
export(NodePath) var default_track_path | |
export(int) var bpm = 140 | |
export(int) var beats_per_bar = 4 | |
var default_fade_duration = _bars_to_seconds(2) | |
onready var default_track: AudioStreamPlayer = get_node(default_track_path) | |
var volume = -INF setget set_volume | |
var is_playing: bool = false | |
func fade_volume(target_volume: float, track: AudioStreamPlayer = default_track, duration: float = default_fade_duration) -> void: | |
var tween = create_tween().set_parallel().set_trans(Tween.TRANS_QUAD) | |
tween.tween_property(track, "volume_db", target_volume, duration) | |
func play() -> void: | |
is_playing = true | |
default_track.play() | |
func pause() -> void: | |
is_playing = false | |
default_track.stop() | |
func set_property(property_name: String, value: float) -> void: | |
pass | |
func set_volume(new_volume: float) -> void: | |
volume = new_volume | |
if default_track: | |
default_track.volume_db = new_volume | |
func _bars_to_seconds(bars: int) -> float: | |
return bars * beats_per_bar * (60.0 / bpm) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment