first commit
This commit is contained in:
157
scripts/audio.gd
Normal file
157
scripts/audio.gd
Normal file
@@ -0,0 +1,157 @@
|
||||
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))
|
||||
Reference in New Issue
Block a user