extends Node ## Spielt Musik und Klänge. Alles wird prozedural erzeugt - in einem ## Thread, damit der Start nicht hakt. Bis dahin bleibt es still. const Sfx = preload("res://scripts/sfx.gd") const Synth = preload("res://scripts/synth.gd") const LOOPS := ["rumble", "squeal"] ## Klänge, die als Schleife laufen const CLACK_DIST := 8.5 ## Meter zwischen zwei Radschlägen const STEP_DIST := 1.75 ## Meter zwischen zwei Schritten const MUSIC_DB := -15.0 const POOL := 10 const POOL_3D := 6 signal ready_for_play() var sounds: Dictionary = {} var music_on := true var loaded := false var music_loaded := false var clacks := 0 ## Zähler, damit sich das testen lässt var last_played := "" ## zuletzt gestarteter Klang var _music: AudioStreamPlayer var _rumble: AudioStreamPlayer var _squeal: AudioStreamPlayer var _pool: Array[AudioStreamPlayer] = [] var _pool3d: Array[AudioStreamPlayer3D] = [] var _next := 0 var _next3d := 0 var _clack_acc := 0.0 var _clack_flip := false var _step_acc := 0.0 var _thread: Thread var _t0 := 0 func _ready() -> void: _music = _make_player(MUSIC_DB) _rumble = _make_player(-80.0) _squeal = _make_player(-80.0) for i in POOL: _pool.append(_make_player(0.0)) for i in POOL_3D: var p3 := AudioStreamPlayer3D.new() p3.unit_size = 14.0 p3.max_distance = 160.0 add_child(p3) _pool3d.append(p3) _t0 = Time.get_ticks_msec() _thread = Thread.new() _thread.start(_build_async) func _make_player(db: float) -> AudioStreamPlayer: var p := AudioStreamPlayer.new() p.volume_db = db add_child(p) return p func _exit_tree() -> void: if _thread != null and _thread.is_started(): _thread.wait_to_finish() _thread = null ## Läuft im Thread: nur Rechnen, keine Godot-Knoten anfassen. func _build_async() -> void: call_deferred("_publish_sfx", Sfx.build_all()) call_deferred("_publish_music", Sfx.build_music()) ## Die kurzen Klänge sind schnell fertig - sie gehen zuerst live. func _publish_sfx(pcm: Dictionary) -> void: for name in pcm.keys(): sounds[name] = Synth.to_stream(pcm[name], LOOPS.has(name)) _rumble.stream = sounds["rumble"] _rumble.play() _squeal.stream = sounds["squeal"] _squeal.play() loaded = true print("[audio] %d Klänge in %d ms" % [sounds.size(), Time.get_ticks_msec() - _t0]) ready_for_play.emit() func _publish_music(pcm: PackedByteArray) -> void: if _thread != null: _thread.wait_to_finish() _thread = null sounds["music"] = Synth.to_stream(pcm, true) _music.stream = sounds["music"] _music.volume_db = MUSIC_DB if music_on else -80.0 _music.play() music_loaded = true print("[audio] Musik in %d ms" % (Time.get_ticks_msec() - _t0)) func toggle_music() -> bool: music_on = not music_on _music.volume_db = MUSIC_DB if music_on else -80.0 return music_on # --- Abspielen ------------------------------------------------------------ func play(name: String, db := 0.0, pitch := 1.0) -> void: if not loaded or not sounds.has(name): return last_played = name var p := _pool[_next] _next = (_next + 1) % POOL p.stream = sounds[name] p.volume_db = db p.pitch_scale = pitch p.play() func play_at(name: String, pos: Vector3, db := 0.0, pitch := 1.0) -> void: if not loaded or not sounds.has(name): return last_played = name var p := _pool3d[_next3d] _next3d = (_next3d + 1) % POOL_3D p.stream = sounds[name] p.global_position = pos p.volume_db = db p.pitch_scale = pitch p.play() # --- Laufende Geräusche --------------------------------------------------- ## speed in m/s, top als Bezugsgröße für Lautstärke und Tonhöhe. func update_train(speed: float, top: float, braking: bool, delta: float) -> void: if not loaded: return var f: float = clampf(speed / maxf(top, 1.0), 0.0, 1.4) if speed < 0.15: _rumble.volume_db = -80.0 else: _rumble.volume_db = linear_to_db(clampf(0.10 + f * 0.65, 0.0, 1.0)) - 6.0 _rumble.pitch_scale = 0.75 + f * 0.55 var squealing: bool = braking and speed > 1.2 var target: float = linear_to_db(clampf(0.15 + f * 0.5, 0.0, 1.0)) - 8.0 if squealing else -80.0 _squeal.volume_db = move_toward(_squeal.volume_db, target, 260.0 * delta) if squealing: _squeal.pitch_scale = 0.8 + f * 0.5 if speed > 0.4: _clack_acc += speed * delta while _clack_acc >= CLACK_DIST: _clack_acc -= CLACK_DIST _clack_flip = not _clack_flip clacks += 1 play("clack2" if _clack_flip else "clack", linear_to_db(clampf(0.25 + f * 0.75, 0.0, 1.0)) - 8.0, 0.85 + f * 0.35) else: _clack_acc = 0.0 ## Schritte, sobald sich der Spieler im Zug bewegt. func update_steps(moving_speed: float, delta: float) -> void: if not loaded or moving_speed < 0.4: _step_acc = STEP_DIST * 0.55 return _step_acc += moving_speed * delta if _step_acc >= STEP_DIST: _step_acc = 0.0 play("step", -19.0, randf_range(0.9, 1.12))