class_name Sfx extends Node ## Primitive Klänge, im Code erzeugt: kurze Töne mit Hüllkurve. ## Keine Audiodateien, passend zu den primitiven Meshes. const RATE := 22050 const HIT_COOLDOWN := 0.06 var _streams := {} var _players: Array = [] var _next := 0 var _hit_gate := 0.0 func _ready() -> void: for i in 8: var pl := AudioStreamPlayer.new() pl.volume_db = -8.0 add_child(pl) _players.append(pl) _streams["kauf"] = _tone([[660.0, 0.07]], 0.5) _streams["verkauf"] = _tone([[520.0, 0.05], [330.0, 0.07]], 0.45) _streams["wuerfeln"] = _noise(0.04, 0.35) _streams["setzen"] = _tone([[180.0, 0.06]], 0.6, true) _streams["aufwerten"] = _tone([[523.0, 0.08], [659.0, 0.08], [784.0, 0.12]], 0.55) _streams["level"] = _tone([[440.0, 0.08], [880.0, 0.14]], 0.5) _streams["treffer"] = _noise(0.03, 0.22) _streams["krit"] = _tone([[220.0, 0.05]], 0.55, true) _streams["heilung"] = _tone([[988.0, 0.06]], 0.3) _streams["zauber"] = _tone([[880.0, 0.05], [1320.0, 0.09]], 0.45) _streams["sieg"] = _tone([[523.0, 0.1], [659.0, 0.1], [784.0, 0.1], [1046.0, 0.2]], 0.6) _streams["niederlage"] = _tone([[392.0, 0.12], [311.0, 0.12], [261.0, 0.24]], 0.6) _streams["runde"] = _tone([[440.0, 0.06], [440.0, 0.06]], 0.4) _streams["fehler"] = _tone([[200.0, 0.1]], 0.5, true) ## Lautstärke 0..1, 0 schaltet stumm. func set_volume(linear: float) -> void: for pl in _players: pl.volume_db = linear_to_db(maxf(0.0001, linear)) - 8.0 pl.stream_paused = false _muted = linear <= 0.001 var _muted := false func play(name: String) -> void: if _muted: return if not _streams.has(name): return if name == "treffer": var now := Time.get_ticks_msec() / 1000.0 if now < _hit_gate: return _hit_gate = now + HIT_COOLDOWN var pl: AudioStreamPlayer = _players[_next] _next = (_next + 1) % _players.size() pl.stream = _streams[name] pl.play() ## Tonfolge [[frequenz, dauer], ...] mit Ein- und Ausblenden. func _tone(notes: Array, gain: float, square: bool = false) -> AudioStreamWAV: var total := 0.0 for n in notes: total += n[1] var frames := int(total * RATE) var data := PackedByteArray() data.resize(frames * 2) var idx := 0 for n in notes: var freq: float = n[0] var len_frames := int(n[1] * RATE) for i in len_frames: var t := float(i) / RATE var env := minf(1.0, float(i) / (0.005 * RATE)) * minf(1.0, float(len_frames - i) / (0.02 * RATE)) var v := sin(TAU * freq * t) if square: v = 1.0 if v >= 0.0 else -1.0 _put(data, idx, v * env * gain) idx += 1 if idx >= frames: break return _wav(data) ## Kurzes Rauschen für Treffer und Würfeln. func _noise(dur: float, gain: float) -> AudioStreamWAV: var frames := int(dur * RATE) var data := PackedByteArray() data.resize(frames * 2) var rng := RandomNumberGenerator.new() rng.seed = 1234 for i in frames: var env := 1.0 - float(i) / float(frames) _put(data, i, rng.randf_range(-1.0, 1.0) * env * env * gain) return _wav(data) func _put(data: PackedByteArray, index: int, v: float) -> void: var s := int(clampf(v, -1.0, 1.0) * 32767.0) data.encode_s16(index * 2, s) func _wav(data: PackedByteArray) -> AudioStreamWAV: var w := AudioStreamWAV.new() w.format = AudioStreamWAV.FORMAT_16_BITS w.mix_rate = RATE w.stereo = false w.data = data return w