extends Node var music_player: AudioStreamPlayer var ambient_loop: AudioStreamPlayer var whale_player: AudioStreamPlayer var swim_loop_player: AudioStreamPlayer var boost_player: AudioStreamPlayer var _whale_timer: Timer var _music_tween: Tween const MUSIC_PATH := "res://audio/music/" const SFX_PATH := "res://audio/sfx/" # Generated stream cache (AudioStreamGenerator-based placeholders) var _gen_streams: Dictionary = {} func _ready() -> void: music_player = AudioStreamPlayer.new() music_player.bus = "Music" music_player.volume_db = -10.0 add_child(music_player) ambient_loop = AudioStreamPlayer.new() ambient_loop.bus = "SFX" ambient_loop.volume_db = -12.0 add_child(ambient_loop) whale_player = AudioStreamPlayer.new() whale_player.bus = "SFX" whale_player.volume_db = -15.0 add_child(whale_player) # Swim loop player (continuous while moving) swim_loop_player = AudioStreamPlayer.new() swim_loop_player.bus = "SFX" swim_loop_player.volume_db = -18.0 add_child(swim_loop_player) # Boost player (one-shot on boost start) boost_player = AudioStreamPlayer.new() boost_player.bus = "SFX" boost_player.volume_db = -10.0 add_child(boost_player) _whale_timer = Timer.new() _whale_timer.one_shot = true _whale_timer.timeout.connect(_on_whale_timer) add_child(_whale_timer) _schedule_next_whale() # Returns (or creates) a silent AudioStreamGenerator for placeholder sounds. func _get_silent_stream(duration_sec: float = 0.5) -> AudioStreamGenerator: var key := "silent_%.1f" % duration_sec if _gen_streams.has(key): return _gen_streams[key] var gen := AudioStreamGenerator.new() gen.mix_rate = 22050.0 gen.buffer_length = duration_sec _gen_streams[key] = gen return gen func play_music(track_name: String) -> void: var path := MUSIC_PATH + track_name if not ResourceLoader.exists(path): return var stream: AudioStream = load(path) if stream == null: return music_player.stream = stream music_player.stream.loop = true music_player.volume_db = -40.0 music_player.play() if _music_tween: _music_tween.kill() _music_tween = create_tween() _music_tween.tween_property(music_player, "volume_db", -10.0, 3.0).set_trans(Tween.TRANS_SINE) func play_ambient_loop(loop_name: String) -> void: var path := SFX_PATH + loop_name if not ResourceLoader.exists(path): # Fall back to silent generator so the slot is occupied ambient_loop.stream = _get_silent_stream(2.0) return var stream: AudioStream = load(path) if stream == null: return ambient_loop.stream = stream ambient_loop.stream.loop = true ambient_loop.play() func play_whale_call_random() -> void: var path := SFX_PATH + "whale_call.ogg" if not ResourceLoader.exists(path): return if whale_player.playing: return var stream: AudioStream = load(path) if stream == null: return whale_player.stream = stream whale_player.play() func play_bubble_sfx(position: Vector3) -> void: var path := SFX_PATH + "bubbles.ogg" var player := AudioStreamPlayer3D.new() player.bus = "SFX" player.volume_db = -8.0 player.max_distance = 20.0 player.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_SQUARE_DISTANCE add_child(player) player.global_position = position if ResourceLoader.exists(path): var stream: AudioStream = load(path) if stream != null: player.stream = stream player.play() player.finished.connect(player.queue_free) return # Placeholder: silent generator player.stream = _get_silent_stream(0.3) player.play() player.finished.connect(player.queue_free) # --- New: swim loop --- func play_swim_loop() -> void: if swim_loop_player.playing: return var path := SFX_PATH + "swim_loop.ogg" if ResourceLoader.exists(path): var s: AudioStream = load(path) if s != null: swim_loop_player.stream = s swim_loop_player.play() return # Silent placeholder keeps the architecture wired swim_loop_player.stream = _get_silent_stream(1.0) swim_loop_player.play() func stop_swim_loop() -> void: if swim_loop_player.playing: swim_loop_player.stop() # --- New: boost sound --- func play_boost() -> void: var path := SFX_PATH + "boost.ogg" if ResourceLoader.exists(path): var s: AudioStream = load(path) if s != null: boost_player.stream = s boost_player.play() return boost_player.stream = _get_silent_stream(0.4) boost_player.play() # --- New: block break --- func play_block_break(position: Vector3) -> void: var path := SFX_PATH + "block_break.ogg" var player := AudioStreamPlayer3D.new() player.bus = "SFX" player.volume_db = -6.0 player.max_distance = 15.0 add_child(player) player.global_position = position if ResourceLoader.exists(path): var s: AudioStream = load(path) if s != null: player.stream = s player.play() player.finished.connect(player.queue_free) return player.stream = _get_silent_stream(0.3) player.play() player.finished.connect(player.queue_free) # --- New: echolocation ping --- func play_echo_ping(position: Vector3) -> void: var path := SFX_PATH + "echo_ping.ogg" var player := AudioStreamPlayer3D.new() player.bus = "SFX" player.volume_db = -4.0 player.max_distance = 40.0 add_child(player) player.global_position = position if ResourceLoader.exists(path): var s: AudioStream = load(path) if s != null: player.stream = s player.play() player.finished.connect(player.queue_free) return player.stream = _get_silent_stream(0.5) player.play() player.finished.connect(player.queue_free) # --- New: pearl collect --- func play_pearl_collect(position: Vector3) -> void: var path := SFX_PATH + "pearl_collect.ogg" var player := AudioStreamPlayer3D.new() player.bus = "SFX" player.volume_db = -5.0 player.max_distance = 20.0 add_child(player) player.global_position = position if ResourceLoader.exists(path): var s: AudioStream = load(path) if s != null: player.stream = s player.play() player.finished.connect(player.queue_free) return player.stream = _get_silent_stream(0.4) player.play() player.finished.connect(player.queue_free) # --- New: death / respawn --- func play_death() -> void: var path := SFX_PATH + "death.ogg" if ResourceLoader.exists(path): var s: AudioStream = load(path) if s != null: var p := AudioStreamPlayer.new() p.bus = "SFX" p.volume_db = -5.0 add_child(p) p.stream = s p.play() p.finished.connect(p.queue_free) return # silent fallback — architecture is wired var p2 := AudioStreamPlayer.new() p2.bus = "SFX" p2.stream = _get_silent_stream(0.6) add_child(p2) p2.play() p2.finished.connect(p2.queue_free) func play_respawn() -> void: var path := SFX_PATH + "respawn.ogg" if ResourceLoader.exists(path): var s: AudioStream = load(path) if s != null: var p := AudioStreamPlayer.new() p.bus = "SFX" p.volume_db = -6.0 add_child(p) p.stream = s p.play() p.finished.connect(p.queue_free) return var p2 := AudioStreamPlayer.new() p2.bus = "SFX" p2.stream = _get_silent_stream(0.5) add_child(p2) p2.play() p2.finished.connect(p2.queue_free) # --- New: ambient deep sea (low drone, played on top of underwater_ambient) --- func play_deep_sea_ambient() -> void: var path := SFX_PATH + "deep_sea_ambient.ogg" var player := AudioStreamPlayer.new() player.bus = "SFX" player.volume_db = -20.0 add_child(player) if ResourceLoader.exists(path): var s: AudioStream = load(path) if s != null: player.stream = s player.play() return player.stream = _get_silent_stream(2.0) player.play() func set_music_volume(db: float) -> void: music_player.volume_db = db func set_sfx_volume(db: float) -> void: ambient_loop.volume_db = db whale_player.volume_db = db - 3.0 func _on_whale_timer() -> void: play_whale_call_random() _schedule_next_whale() func _schedule_next_whale() -> void: _whale_timer.wait_time = randf_range(30.0, 90.0) _whale_timer.start()