class_name ArenaAudio extends Node var sounds := {} var shot_players := {} var rng := RandomNumberGenerator.new() func _ready() -> void: rng.seed = 721 var limited := false for i in AudioServer.get_bus_effect_count(0): limited = limited or AudioServer.get_bus_effect(0, i) is AudioEffectLimiter if not limited: var limiter := AudioEffectLimiter.new() limiter.ceiling_db = -0.5 AudioServer.add_bus_effect(0, limiter) for kind in ["shot", "step", "reload", "explosion", "hit", "death", "flash", "melee", "confirm"]: sounds[kind] = synthesize(kind) for weapon in Arsenal.DATA.size(): sounds["shot_%s" % weapon] = synthesize("shot", weapon) func synthesize(kind: String, weapon: int = 0) -> AudioStreamWAV: var duration: float = {"melee": 0.18, "confirm": 0.24, "shot": 0.16, "step": 0.08, "reload": 0.3, "explosion": 1.8, "flash": 0.22, "hit": 0.08, "death": 0.25}[kind] if kind == "shot": duration = [0.20, 0.13, 0.34, 0.42, 0.15, 0.28, 0.24, 0.26, 0.10, 0.27][weapon] var data := PackedByteArray() var count := int(duration * 22050) data.resize(count * 2) var filtered := 0.0 for i in count: var t := float(i) / 22050.0 var envelope := pow(1.0 - float(i) / count, 2) var noise := rng.randf_range(-1, 1) filtered = lerpf(filtered, noise, 0.16) var value := noise * 0.5 + sin(t * 520) * 0.5 match kind: "shot": var bass: float = [105.0, 165.0, 65.0, 52.0, 190.0, 85.0, 72.0, 92.0, 220.0, 62.0][weapon] value = noise * exp(-t * 65) * 0.8 + filtered * exp(-t * 12) * 1.8 + sin(TAU * bass * t) * exp(-t * 18) * 0.6 "melee": value = filtered * 2.0 + sin(t * 210) * 0.3 "confirm": value = sin(t * (5400 if t < 0.1 else 7200)) * 0.32 "step": value = filtered * 2 + sin(t * 380) * 0.2 "reload": value = noise * (0.5 if fmod(t, 0.1) < 0.025 else 0.04) "explosion": value = filtered * 2.7 + sin(TAU * (48 * t - 7 * t * t)) * 0.7 + noise * exp(-t * 32) * 0.5 "flash": value = noise * 0.7 + sin(t * 2500) * 0.25 "hit": value = sin(t * 6200) * 0.4 "death": value = sin(t * (900 - t * 1800)) * 0.4 data.encode_s16(i * 2, int(clampf(value * envelope * 0.7, -1, 1) * 32767)) var wav := AudioStreamWAV.new() wav.format = AudioStreamWAV.FORMAT_16_BITS wav.mix_rate = 22050 wav.data = data return wav func play_at(kind: String, pos: Vector3, local: bool = false, variant: int = 0, emitter: int = 0) -> void: if DisplayServer.get_name() == "headless" or not sounds.has(kind): return var sound_key := "shot_%s" % clampi(variant, 0, Arsenal.DATA.size() - 1) if kind == "shot" else kind if kind == "shot": play_shot(sound_key, pos, local, emitter) return if local: var player := AudioStreamPlayer.new() player.stream = sounds[sound_key] player.volume_db = -14 if kind == "step" else -8 add_child(player) player.finished.connect(player.queue_free) player.play() else: var player := AudioStreamPlayer3D.new() player.stream = sounds[sound_key] player.unit_size = 18 if kind == "explosion" else (9 if kind == "shot" else 4) player.max_distance = 75 if kind != "step" else 22 player.volume_db = -8 player.pitch_scale = 1.0 add_child(player) player.global_position = pos player.finished.connect(player.queue_free) player.play() func play_shot(key: String, pos: Vector3, local: bool, emitter: int = 0) -> void: var pool_key := key + ("_local" if local else "_world_%s" % emitter) if not shot_players.has(pool_key): var node: Node = AudioStreamPlayer.new() if local else AudioStreamPlayer3D.new() node.stream = sounds[key] node.max_polyphony = 3 node.volume_db = -8 if not local: node.unit_size = 9 node.max_distance = 75 add_child(node) shot_players[pool_key] = node var player: Node = shot_players[pool_key] player.pitch_scale = randf_range(0.95, 1.05) if not local: player.global_position = pos player.play() func _exit_tree() -> void: for player in shot_players.values(): if is_instance_valid(player): player.stop() shot_players.clear()