140 lines
5.2 KiB
GDScript
140 lines
5.2 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
|
|
if is_instance_valid(source.weather): gust += source.weather.intensity*2.5
|
|
_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()
|