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)