Randoloot/scripts/planets/freighter_audio.gd
2026-10-18 15:11:06 +02:00

135 lines
4.7 KiB
GDScript

extends Node
## Quiet industrial foley built from CC0 recordings; lives only inside the deck.
const ROOT := "res://assets/sounds/freighter/"
const VOICE_LIMIT := 4
var _streams: Dictionary = {}
var _beds: Array[AudioStreamPlayer] = []
var _voices: Array[AudioStreamPlayer] = []
var _panners: Array[AudioEffectPanner] = []
var _buses: Array[StringName] = []
var _rng := RandomNumberGenerator.new()
var _elapsed := 0.0
var _last_action := -10.0
var _last_service := -10.0
var _last_section := ""
var _next_tool := 9.0
var _next_console := 18.0
var _next_pressure := 32.0
var _last_tool := ""
var played_count := 0
var _maintenance_needed := false
var _alarm_due := INF
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
_rng.randomize()
for id in ["reactor_loop","ventilation_loop","tool_1","tool_2","tool_3","metal_1","metal_2","airlock_open","airlock_close","pressure_release","docking_clamp","service_chime","maintenance_alarm"]:
_streams[id] = load(ROOT+id+".wav")
for id in ["console_1","console_2"]: _streams[id] = load(ROOT+id+".ogg")
_start_bed("reactor_loop",-14.0)
_start_bed("ventilation_loop",-21.0)
for index in VOICE_LIMIT:
var bus := StringName("Frigate_%d_%d" % [get_instance_id(),index])
AudioServer.add_bus()
var bus_index := AudioServer.bus_count-1
AudioServer.set_bus_name(bus_index,bus)
AudioServer.set_bus_send(bus_index,&"Master")
var panner := AudioEffectPanner.new()
AudioServer.add_bus_effect(bus_index,panner)
_buses.append(bus)
_panners.append(panner)
var voice := AudioStreamPlayer.new()
voice.name = "Foley%d" % index
voice.bus = bus
add_child(voice)
_voices.append(voice)
play_effect("docking_clamp",0.65,-13.0,true)
_next_tool = _rng.randf_range(8,15)
_next_console = _rng.randf_range(18,30)
_next_pressure = _rng.randf_range(32,55)
func _start_bed(id: String, level: float) -> void:
var player := AudioStreamPlayer.new()
player.name = id.to_pascal_case()
var stream := (_streams[id] as AudioStreamWAV).duplicate() as AudioStreamWAV
stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
stream.loop_begin = 0
stream.loop_end = roundi(stream.get_length()*stream.mix_rate)
player.stream = stream
player.volume_db = -60
add_child(player)
player.play()
_beds.append(player)
create_tween().tween_property(player,"volume_db",level,1.2)
func play_effect(id: String, pan: float, level := -15.0, important := false) -> bool:
if not _streams.has(id): return false
var available := -1
for index in _voices.size():
if not _voices[index].playing:
available = index
break
if available < 0:
if not important: return false
available = 0
for index in _voices.size():
if float(_voices[index].get_meta("started",0.0)) < float(_voices[available].get_meta("started",0.0)): available = index
var voice := _voices[available]
voice.stop()
voice.stream = _streams[id]
voice.volume_db = level
voice.pitch_scale = 1.0 if id == "service_chime" else _rng.randf_range(0.97,1.03)
_panners[available].pan = clampf(pan,-0.75,0.75)
voice.set_meta("started",_elapsed)
voice.play()
played_count += 1
return true
func enter_section(id: String, pan: float) -> void:
if id == _last_section or _elapsed-_last_action < 0.7: return
_last_action = _elapsed
_last_section = id
play_effect("airlock_open",pan,-13.5,true)
func close_section() -> void:
if _last_section.is_empty(): return
_last_section = ""
if _elapsed-_last_action < 0.7: return
_last_action = _elapsed
play_effect("airlock_close",0.0,-17.0)
func service_completed(pan := 0.0) -> void:
if _elapsed-_last_service < 0.7: return
_last_service = _elapsed
play_effect("service_chime",pan,-20.0)
func set_maintenance_needed(value: bool) -> void:
if value and not _maintenance_needed: _alarm_due = _elapsed+4.0
if not value: _alarm_due = INF
_maintenance_needed = value
func _process(delta: float) -> void:
_elapsed += delta
if _elapsed >= _alarm_due:
play_effect("maintenance_alarm",0.6,-22.0)
_alarm_due = INF # One advisory per damaged-lander visit, no false emergency loop.
if _elapsed >= _next_tool:
var choices := ["tool_1","tool_2","tool_3","metal_1","metal_2"]
choices.erase(_last_tool)
_last_tool = choices[_rng.randi_range(0,choices.size()-1)]
play_effect(_last_tool,-0.48,_rng.randf_range(-17,-13))
_next_tool = _elapsed+_rng.randf_range(10,23)
if _elapsed >= _next_console:
play_effect("console_%d" % _rng.randi_range(1,2),-0.7,-25.0)
_next_console = _elapsed+_rng.randf_range(24,48)
if _elapsed >= _next_pressure:
play_effect("pressure_release",0.6,-23.0)
_next_pressure = _elapsed+_rng.randf_range(38,75)
func _exit_tree() -> void:
for voice in _voices: voice.stop()
for bed in _beds: bed.stop()
for bus in _buses:
var index := AudioServer.get_bus_index(bus)
if index >= 0: AudioServer.remove_bus(index)