Files
train-game/scripts/audio.gd
2026-08-29 11:44:31 +02:00

158 lines
4.5 KiB
GDScript

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 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:
var pcm: Dictionary = Sfx.build_all()
pcm["music"] = Sfx.build_music()
call_deferred("_finish_build", pcm)
func _finish_build(pcm: Dictionary) -> void:
if _thread != null:
_thread.wait_to_finish()
_thread = null
for name in pcm.keys():
var loop: bool = name == "music" or LOOPS.has(name)
sounds[name] = Synth.to_stream(pcm[name], loop)
_music.stream = sounds["music"]
_music.volume_db = MUSIC_DB if music_on else -80.0
_music.play()
_rumble.stream = sounds["rumble"]
_rumble.play()
_squeal.stream = sounds["squeal"]
_squeal.play()
loaded = true
print("[audio] %d Klänge in %d ms erzeugt" % [sounds.size(), Time.get_ticks_msec() - _t0])
ready_for_play.emit()
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))