extends RefCounted ## Alle Klänge des Spiels, einmal beim Start berechnet. const Synth = preload("res://scripts/synth.gd") const BPM := 72.0 const BEAT := 60.0 / BPM const BAR := BEAT * 4.0 const BARS := 8 ## Eine Stufe der Harmoniefolge. Alle Akkorde liegen in derselben Lage ## (F3..G4), damit kein Takt aus dem Klangbild springt. "melody" enthält ## nur Töne, die zum Akkord passen. Geprüft wird auf kleine Sekunde und ## kleine None gegen jede Stimme - genau die reiben hörbar. Über G6 fehlt ## deshalb das C, über Cmaj7 ebenfalls. const PROGRESSION := [ {"name": "Am7", "pad": [220.00, 261.63, 329.63, 392.00], "bass": 110.00, "melody": [440.00, 493.88, 523.25, 587.33, 659.26, 783.99]}, {"name": "Fmaj7", "pad": [174.61, 220.00, 261.63, 329.63], "bass": 87.31, "melody": [440.00, 523.25, 587.33, 659.26, 783.99, 880.00]}, {"name": "Cmaj7", "pad": [196.00, 246.94, 329.63, 392.00], "bass": 130.81, "melody": [493.88, 587.33, 659.26, 783.99, 880.00]}, {"name": "G6", "pad": [196.00, 246.94, 293.66, 329.63], "bass": 98.00, "melody": [440.00, 493.88, 587.33, 659.26, 783.99]}, {"name": "Am7", "pad": [220.00, 261.63, 329.63, 392.00], "bass": 110.00, "melody": [440.00, 493.88, 523.25, 587.33, 659.26, 783.99]}, {"name": "Fmaj7", "pad": [174.61, 220.00, 261.63, 329.63], "bass": 87.31, "melody": [440.00, 523.25, 587.33, 659.26, 783.99, 880.00]}, {"name": "Dm7", "pad": [220.00, 261.63, 293.66, 349.23], "bass": 146.83, "melody": [440.00, 523.25, 587.33, 659.26, 698.46, 783.99]}, {"name": "G6", "pad": [196.00, 246.94, 293.66, 329.63], "bass": 98.00, "melody": [440.00, 493.88, 587.33, 659.26, 783.99]}, ] ## Zweiter Teil: geht über Dm7 und Em7, kommt über G6 zurück. const PROGRESSION_B := [ {"name": "Dm7", "pad": [220.00, 261.63, 293.66, 349.23], "bass": 146.83, "melody": [440.00, 523.25, 587.33, 659.26, 698.46, 783.99]}, {"name": "Em7", "pad": [196.00, 246.94, 293.66, 329.63], "bass": 164.81, "melody": [493.88, 587.33, 659.26, 783.99, 880.00]}, {"name": "Fmaj7", "pad": [174.61, 220.00, 261.63, 329.63], "bass": 87.31, "melody": [440.00, 523.25, 587.33, 659.26, 783.99, 880.00]}, {"name": "G6", "pad": [196.00, 246.94, 293.66, 329.63], "bass": 98.00, "melody": [440.00, 493.88, 587.33, 659.26, 783.99]}, {"name": "Am7", "pad": [220.00, 261.63, 329.63, 392.00], "bass": 110.00, "melody": [440.00, 493.88, 523.25, 587.33, 659.26, 783.99]}, {"name": "Em7", "pad": [196.00, 246.94, 293.66, 329.63], "bass": 164.81, "melody": [493.88, 587.33, 659.26, 783.99, 880.00]}, {"name": "Fmaj7", "pad": [174.61, 220.00, 261.63, 329.63], "bass": 87.31, "melody": [440.00, 523.25, 587.33, 659.26, 783.99, 880.00]}, {"name": "G6", "pad": [196.00, 246.94, 293.66, 329.63], "bass": 98.00, "melody": [440.00, 493.88, 587.33, 659.26, 783.99]}, ] ## Reihenfolge der Teile: A, B, A mit anderer Melodie. const FORM := [0, 1, 0] const SECTION_SEEDS := [7411, 9187, 3323] static func build_all() -> Dictionary: var rng := RandomNumberGenerator.new() rng.seed = 20260828 return { "clack": _clack(rng), "clack2": _clack2(rng), "rumble": _rumble(rng), "squeal": _squeal(rng), "whistle": _whistle(rng), "hiss": _hiss(rng), "bell": _bell(), "coin": _coin(), "pour": _pour(rng), "clink": _clink(), "thud": _thud(rng), "step": _step(rng), "click": _click(), "deny": _deny(), } static func build_music() -> PackedByteArray: var section_len := BAR * BARS var loop_len := section_len * FORM.size() var buf := Synth.buffer(loop_len + 3.0) for sec in FORM.size(): var prog: Array = PROGRESSION if FORM[sec] == 0 else PROGRESSION_B var rng := RandomNumberGenerator.new() rng.seed = int(SECTION_SEEDS[sec]) _render_section(buf, sec * section_len, prog, rng) return Synth.to_pcm(Synth.seamless(buf, loop_len)) static func _render_section(buf: PackedFloat32Array, offset: float, prog: Array, rng: RandomNumberGenerator) -> void: for b in BARS: var t0 := offset + b * BAR var stage: Dictionary = prog[b] var pad: Array = stage["pad"] var bass: float = float(stage["bass"]) var melody: Array = stage["melody"] for i in pad.size(): var amp: float = 0.19 / (1.0 + i * 0.4) Synth.add_pad(buf, t0 - 0.45, float(pad[i]), BAR + 1.0, amp, 1.2) Synth.add_tone(buf, t0, bass, 2.3, 0.36, 1.4, [1.0, 0.38, 0.12], 0.01) Synth.add_tone(buf, t0 + BEAT * 2.0, bass * 1.5, 1.7, 0.20, 1.8, [1.0, 0.28], 0.01) var beats := [0.0, 1.5, 2.5] if b % 2 == 0 else [0.5, 2.0, 3.0] for k in beats.size(): if rng.randf() < 0.3: continue var f: float = float(melody[rng.randi() % melody.size()]) Synth.add_tone(buf, t0 + float(beats[k]) * BEAT, f, 2.4, 0.11, 2.2, [1.0, 0.45, 0.18, 0.07], 0.006) for beat in 4: Synth.add_noise(buf, t0 + beat * BEAT, 0.12, 0.032 if beat % 2 == 1 else 0.020, 34.0, 0.62, rng) # --- Einzelklänge --------------------------------------------------------- ## Ein Radstoß: tiefer Schlag, mittleres Klopfen, kurzer Metallanteil. static func _impact(b: PackedFloat32Array, t: float, low: float, mid: float, amp: float, rng: RandomNumberGenerator) -> void: Synth.add_tone(b, t, low, 0.30, 0.85 * amp, 13.0, [1.0, 0.30, 0.10], 0.002) Synth.add_tone(b, t, mid, 0.22, 0.42 * amp, 26.0, [1.0, 0.35], 0.002) Synth.add_noise(b, t, 0.16, 0.30 * amp, 30.0, 0.14, rng, 0.001) Synth.add_noise(b, t, 0.05, 0.20 * amp, 90.0, 0.55, rng, 0.001) Synth.add_resonant_noise(b, t, 0.14, 1450.0, 0.985, 0.16 * amp, rng, 26.0) ## Doppelschlag der beiden Achsen eines Drehgestells. static func _clack(rng: RandomNumberGenerator) -> PackedByteArray: var b := Synth.buffer(0.62) _impact(b, 0.0, 61.0, 196.0, 1.0, rng) _impact(b, 0.155, 58.0, 183.0, 0.72, rng) return Synth.to_pcm(b) static func _clack2(rng: RandomNumberGenerator) -> PackedByteArray: var b := Synth.buffer(0.62) _impact(b, 0.0, 68.0, 213.0, 0.94, rng) _impact(b, 0.168, 64.0, 172.0, 0.66, rng) return Synth.to_pcm(b) ## Grundrauschen der Fahrt, als Schleife. static func _rumble(rng: RandomNumberGenerator) -> PackedByteArray: var loop := 1.5 var b := Synth.buffer(loop + 0.4) var y := 0.0 var y2 := 0.0 for i in b.size(): y += (rng.randf_range(-1.0, 1.0) - y) * 0.06 y2 += (y - y2) * 0.09 b[i] = y2 * 3.4 for k in 3: Synth.add_tone(b, 0.0, 46.0 + k * 23.0, loop + 0.4, 0.1 / (k + 1), 0.0, [1.0], 0.05) return Synth.to_pcm(Synth.seamless(b, loop)) ## Bremsenquietschen. Der tonale Teil ist exakt periodisch zur Schleifen- ## länge, der Rauschanteil wird übergeblendet - so klickt die Naht nicht. static func _squeal(rng: RandomNumberGenerator) -> PackedByteArray: var loop := 1.2 var unit := 1.0 / loop ## alles Vielfache davon var noise := Synth.buffer(loop + 0.35) Synth.add_resonant_noise(noise, 0.0, loop + 0.35, 1740.0, 0.9955, 0.55, rng) Synth.add_resonant_noise(noise, 0.0, loop + 0.35, 2610.0, 0.9945, 0.28, rng) Synth.add_noise(noise, 0.0, loop + 0.35, 0.05, 0.0, 0.5, rng, 0.02) var b := Synth.seamless(noise, loop) Synth.add_wobble(b, 0.0, unit * 1740.0, loop, 0.26, 0.010, unit * 4.0, 0.12) Synth.add_wobble(b, 0.0, unit * 2610.0, loop, 0.13, 0.008, unit * 7.0) return Synth.to_pcm(b) static func _whistle(rng: RandomNumberGenerator) -> PackedByteArray: var b := Synth.buffer(1.6) for f in [523.0, 659.0, 784.0]: Synth.add_tone(b, 0.0, float(f), 1.45, 0.30, 1.1, [1.0, 0.45, 0.2, 0.08], 0.09, 0.012, 5.5) Synth.add_tone(b, 0.0, float(f) * 1.004, 1.45, 0.22, 1.1, [1.0, 0.3], 0.11) Synth.add_noise(b, 0.0, 1.4, 0.12, 1.4, 0.55, rng, 0.1) return Synth.to_pcm(b) static func _hiss(rng: RandomNumberGenerator) -> PackedByteArray: var b := Synth.buffer(1.3) Synth.add_noise(b, 0.0, 1.2, 0.7, 3.2, 0.75, rng, 0.03) Synth.add_noise(b, 0.05, 1.0, 0.3, 2.4, 0.35, rng, 0.05) return Synth.to_pcm(b) static func _bell() -> PackedByteArray: var b := Synth.buffer(1.9) for k in 2: var t := k * 0.34 Synth.add_tone(b, t, 987.77, 1.5, 0.34, 2.6, [1.0, 0.42, 0.18, 0.09], 0.004) Synth.add_tone(b, t, 1318.51, 1.4, 0.20, 3.0, [1.0, 0.3], 0.004) return Synth.to_pcm(b) static func _coin() -> PackedByteArray: var b := Synth.buffer(0.5) Synth.add_tone(b, 0.0, 1174.66, 0.3, 0.4, 11.0, [1.0, 0.4, 0.15], 0.003) Synth.add_tone(b, 0.07, 1567.98, 0.42, 0.4, 8.0, [1.0, 0.35, 0.12], 0.003) Synth.add_tone(b, 0.07, 2349.32, 0.35, 0.15, 10.0, [1.0], 0.003) return Synth.to_pcm(b) static func _pour(rng: RandomNumberGenerator) -> PackedByteArray: var b := Synth.buffer(0.95) Synth.add_noise(b, 0.0, 0.9, 0.4, 1.1, 0.42, rng, 0.08) for i in 9: var t := 0.1 + i * 0.085 Synth.add_tone(b, t, rng.randf_range(700.0, 1500.0), 0.09, 0.1, 30.0, [1.0], 0.004) return Synth.to_pcm(b) static func _clink() -> PackedByteArray: var b := Synth.buffer(0.6) Synth.add_tone(b, 0.0, 2637.02, 0.5, 0.3, 7.0, [1.0, 0.3], 0.002) Synth.add_tone(b, 0.0, 3520.0, 0.4, 0.16, 9.0, [1.0], 0.002) Synth.add_tone(b, 0.11, 2793.83, 0.4, 0.2, 8.0, [1.0], 0.002) return Synth.to_pcm(b) static func _thud(rng: RandomNumberGenerator) -> PackedByteArray: var b := Synth.buffer(0.4) Synth.add_tone(b, 0.0, 68.0, 0.3, 0.65, 13.0, [1.0, 0.3], 0.003) Synth.add_noise(b, 0.0, 0.14, 0.3, 34.0, 0.28, rng) return Synth.to_pcm(b) ## Schritt: Ferse zuerst, dann das Abrollen. Ohne den tiefen Anteil ## bleibt nur ein heller Tick übrig. static func _step(rng: RandomNumberGenerator) -> PackedByteArray: var b := Synth.buffer(0.34) Synth.add_tone(b, 0.0, 84.0, 0.26, 0.55, 15.0, [1.0, 0.28, 0.08], 0.003) Synth.add_tone(b, 0.0, 137.0, 0.16, 0.22, 26.0, [1.0], 0.003) Synth.add_noise(b, 0.0, 0.20, 0.26, 21.0, 0.12, rng, 0.003) Synth.add_noise(b, 0.035, 0.12, 0.10, 34.0, 0.42, rng, 0.012) return Synth.to_pcm(b) static func _click() -> PackedByteArray: var b := Synth.buffer(0.12) Synth.add_tone(b, 0.0, 1720.0, 0.06, 0.28, 60.0, [1.0, 0.4], 0.001) Synth.add_tone(b, 0.02, 1180.0, 0.07, 0.2, 45.0, [1.0], 0.001) return Synth.to_pcm(b) static func _deny() -> PackedByteArray: var b := Synth.buffer(0.35) Synth.add_tone(b, 0.0, 320.0, 0.14, 0.3, 16.0, [1.0, 0.4], 0.004) Synth.add_tone(b, 0.12, 240.0, 0.2, 0.3, 13.0, [1.0, 0.4], 0.004) return Synth.to_pcm(b)