Skip to content

Instantly share code, notes, and snippets.

@lemilonkh
Last active July 27, 2023 07:16
Show Gist options
  • Save lemilonkh/c03715e67cb3b1e14875a851eb978a58 to your computer and use it in GitHub Desktop.
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)
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()
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)
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()
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()
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