Randoloot/scripts/planets/surface_audio.gd
2026-10-18 16:22:39 +02:00

139 lines
5.1 KiB
GDScript

extends Node
## Wind beds plus sparse, localised foliage and wildlife field recordings.
const ROOT := "res://assets/sounds/surface/"
const VOICE_LIMIT := 3
const WIND_DB := [-18.0,-17.0,-16.0,-13.0,-12.0,-14.0]
var source: Control
var _streams: Dictionary = {}
var _wind: AudioStreamPlayer
var _foliage: AudioStreamPlayer
var _world_audio: Node2D
var _listener: AudioListener2D
var _voices: Array[AudioStreamPlayer2D] = []
var _rng := RandomNumberGenerator.new()
var _elapsed := 0.0
var _local_refresh := 0.0
var _near_trees: Array[Vector2] = []
var _density := 0.0
var _biome := 0
var _oxygen := 100.0
var _next_gust := 10.0
var _next_leaves := 6.0
var _next_creak := 20.0
var _next_fauna := 12.0
var _last_leaf := ""
var _last_bird := ""
var played_count := 0
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
_biome = int(source.world.data.landscape.index)
_oxygen = float(source.progress.active_profile.get("oxygen",100))
_rng.seed = int(source.progress.visit_seed)^0x4e415455
for id in ["wind_loop","wind_gust","foliage_loop","leaves_1","leaves_2","leaves_3","branch_creak_1","branch_creak_2","bird_1","bird_2","bird_3","insects","marsh_call"]:
_streams[id] = load(ROOT+id+".wav")
_wind = _bed("wind_loop")
_foliage = _bed("foliage_loop")
_world_audio = Node2D.new()
_world_audio.name = "NatureAudioWorld"
_world_audio.process_mode = Node.PROCESS_MODE_ALWAYS
source._surface_viewport.audio_listener_enable_2d = true
source._surface_viewport.add_child(_world_audio)
_listener = AudioListener2D.new()
_world_audio.add_child(_listener)
_listener.make_current()
for index in VOICE_LIMIT:
var voice := AudioStreamPlayer2D.new()
voice.name = "NatureVoice%d" % index
voice.max_distance = 1050
voice.attenuation = 1.1
voice.panning_strength = 0.65
_world_audio.add_child(voice)
_voices.append(voice)
_refresh_local()
_next_gust = _rng.randf_range(8,16)
_next_fauna = _rng.randf_range(7,15)
func _bed(id: String) -> AudioStreamPlayer:
var bed := AudioStreamPlayer.new()
var stream := (_streams[id] as AudioStreamWAV).duplicate() as AudioStreamWAV
stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
stream.loop_end = roundi(stream.get_length()*stream.mix_rate)
bed.stream = stream
bed.volume_db = -48
add_child(bed)
bed.play()
return bed
func _refresh_local() -> void:
_near_trees.clear()
for tree in source.world.data.trees:
if source.lander.position.distance_squared_to(tree.position) < 560.0*560.0:
_near_trees.append(tree.position)
_density = clampf(float(_near_trees.size())/22.0,0,1)
func fauna_choices() -> Array[String]:
if _oxygen < 12: return []
match _biome:
2: return ["marsh_call","insects","bird_2"]
4: return ["insects"]
5: return ["bird_3"]
_: return ["bird_1","bird_2","bird_3","insects"]
func _play(id: String, position: Vector2, level: float) -> bool:
for voice in _voices:
if voice.playing: continue
voice.stream = _streams[id]
voice.position = position
voice.volume_db = level
voice.pitch_scale = _rng.randf_range(0.95,1.03)
voice.play()
played_count += 1
return true
return false
func _tree_position() -> Vector2:
return _near_trees[_rng.randi_range(0,_near_trees.size()-1)]
func _process(delta: float) -> void:
if not is_instance_valid(source) or not is_instance_valid(_listener): return
_elapsed += delta
_local_refresh += delta
_listener.position = source.lander.position
if _local_refresh > 0.65:
_local_refresh = 0
_refresh_local()
var gust := sin(_elapsed*0.19)*1.4+sin(_elapsed*0.073)*0.8
var engine_mask := -3.0 if source.engine_thrust_active() else 0.0
_wind.volume_db = move_toward(_wind.volume_db,WIND_DB[_biome]+gust+engine_mask,delta*26)
var leaf_target := -60.0 if _near_trees.is_empty() else lerpf(-34,-23,_density)+gust+engine_mask
_foliage.volume_db = move_toward(_foliage.volume_db,leaf_target,delta*8)
if _elapsed >= _next_gust:
_play("wind_gust",_listener.position+Vector2(_rng.randf_range(-250,250),-120),-23.0 if _biome < 3 else -20.0)
_next_gust = _elapsed+_rng.randf_range(18,38)
if _elapsed >= _next_leaves:
if not _near_trees.is_empty():
var choices := ["leaves_1","leaves_2","leaves_3"]
choices.erase(_last_leaf)
_last_leaf = choices[_rng.randi_range(0,choices.size()-1)]
_play(_last_leaf,_tree_position(),-14.0)
_next_leaves = _elapsed+_rng.randf_range(9,20)
if _elapsed >= _next_creak:
if not _near_trees.is_empty(): _play("branch_creak_%d" % _rng.randi_range(1,2),_tree_position(),-17.0)
_next_creak = _elapsed+_rng.randf_range(26,52)
if _elapsed >= _next_fauna:
var choices := fauna_choices()
if choices.size() > 1: choices.erase(_last_bird)
if not choices.is_empty():
_last_bird = choices[_rng.randi_range(0,choices.size()-1)]
var at := _listener.position+Vector2.from_angle(_rng.randf_range(-PI,PI))*_rng.randf_range(200,520)
_play(_last_bird,at,-15.0 if _biome < 3 else -19.0)
_next_fauna = _elapsed+_rng.randf_range(15,33)*(2.0 if _biome in [3,4,5] else 1.0)
func _exit_tree() -> void:
if is_instance_valid(_wind): _wind.stop()
if is_instance_valid(_foliage): _foliage.stop()
for voice in _voices:
if is_instance_valid(voice): voice.stop()
if is_instance_valid(_world_audio): _world_audio.queue_free()