extends Node # Dependency-free audio foundation. Authored assets can replace these tones # later without changing gameplay call sites or the multiplayer event flow. const SAMPLE_RATE := 44100 const MAX_INTENSITY := 1.0 var enabled := true func bind_tree_buttons(root: Node) -> void: if root == null: return for node in root.find_children("*", "BaseButton", true, false): bind_button(node as BaseButton) func bind_button(button: BaseButton) -> void: if button == null: return var callback := Callable(self, "play_ui_click") if not button.pressed.is_connected(callback): button.pressed.connect(callback) func play_ui_click() -> void: _play_tone(880.0, 0.045, 0.10) func play_countdown(count: int) -> void: if count <= 0: _play_tone(1046.5, 0.12, 0.18) else: _play_tone(countdown_frequency(count), 0.08, 0.14) func play_impact(intensity: float) -> void: var amount := clamp_intensity(intensity) if amount <= 0.0: return _play_tone(150.0 + 180.0 * amount, 0.06 + 0.08 * amount, 0.08 + 0.18 * amount) func play_goal() -> void: _play_tone(523.25, 0.22, 0.22) _play_tone(783.99, 0.30, 0.18) static func clamp_intensity(value: float) -> float: if not is_finite(value): return 0.0 return clampf(value, 0.0, MAX_INTENSITY) static func countdown_frequency(count: int) -> float: return 440.0 + float(clampi(count, 1, 9)) * 55.0 func _play_tone(frequency: float, duration: float, volume: float) -> void: if not enabled or frequency <= 0.0 or duration <= 0.0 or volume <= 0.0: return var stream := AudioStreamWAV.new() stream.format = AudioStreamWAV.FORMAT_16_BITS stream.mix_rate = SAMPLE_RATE stream.stereo = false stream.data = _tone_data(frequency, duration, volume) var player := AudioStreamPlayer.new() player.stream = stream add_child(player) player.finished.connect(player.queue_free) player.play() func _tone_data(frequency: float, duration: float, volume: float) -> PackedByteArray: var frames := maxi(1, int(duration * SAMPLE_RATE)) var data := PackedByteArray() data.resize(frames * 2) for index in frames: var envelope := minf(1.0, float(index) / 256.0) * minf(1.0, float(frames - index) / 1024.0) var sample := int(sin(TAU * frequency * float(index) / SAMPLE_RATE) * volume * envelope * 32767.0) if sample < 0: sample += 65536 data[index * 2] = sample & 0xff data[index * 2 + 1] = (sample >> 8) & 0xff return data