163 lines
4.7 KiB
GDScript
163 lines
4.7 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 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))
|