Initial commit — Godot space roguelite source

- Touch controls: direct InputEventScreenTouch in shop_ui (bypass relay)
- ItemDB: static preload list instead of DirAccess scan (export fix)
- All 18 items with EN localisation (name_en, desc_en, category_en)
- Ship playstyles: NOVA-1 shield, INFERNO ram, AURORA agile/tank
- Quasar: SMBH visual, jet boost, merge, push, BH-eating
- Atlas & UI text updated EN+DE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 14:38:09 +02:00
commit edc40f9008
108 changed files with 10068 additions and 0 deletions
+623
View File
@@ -0,0 +1,623 @@
extends Node
# ═══════════════════════════════════════════════════════════════════════════
# "STELLAR DRIFT" · Normal-OST (A-Moll, 140 BPM, Am→G→F→Em)
# "CRITICAL MASS" · Boss-OST #1 WRAITH (A-Moll, 175 BPM, Super Hexagon)
# "EVENT HORIZON" · Boss-OST #2 LEVIATHAN (E-Phrygisch, 140 BPM, Cosmic Dread)
#
# Voices in einem AudioStreamPlayer auf dem "Music"-Bus:
# Voice 0 · Melodie · Triangle (normal/boss2), Square (boss1)
# Voice 1 · Arpeggio · Square (normal/boss1), Triangle leise (boss2)
# Voice 2 · Bass · Sawtooth (boss2: +detuneter 2. Layer für Schwebung)
# Voice 3 · Schlagzeug · Hi-Hat+Kick+Snare (normal/boss1) ·
# Kick+Floor-Tom+Mid-Tom+Reverse-Whoosh (boss2)
# Voice 4 · Phase-2-Layer · Triangle 1 Oktave über Melodie (nur boss2 Phase 2)
# ═══════════════════════════════════════════════════════════════════════════
@export var volume_db: float = -8.0
@export var loop: bool = true
@export var autoplay: bool = true
const SAMPLE_RATE := 44100
const BUFFER_SIZE := 0.1
# ── Normale Musik ─────────────────────────────────────────────────────────
const BPM := 140.0
const BEAT := 60.0 / BPM
# ── Boss-Musik #1 (WRAITH) ────────────────────────────────────────────────
const BOSS_BPM := 175.0
const BOSS_BEAT := 60.0 / BOSS_BPM
# ── Boss-Musik #2 (LEVIATHAN) ─────────────────────────────────────────────
const BOSS2_BPM := 140.0
const BOSS2_BEAT := 60.0 / BOSS2_BPM
# ── Frequenztabelle ───────────────────────────────────────────────────────
const FREQ := {
"R" : 0.0,
"D2" : 73.42, "E2" : 82.41, "F2" : 87.31, "A2" : 110.00,
"C3" : 130.81, "D3" : 146.83, "E3" : 164.81,
"F3" : 174.61, "G3" : 196.00, "A3" : 220.00, "B3" : 246.94,
"C4" : 261.63, "D4" : 293.66, "E4" : 329.63,
"F4" : 349.23, "G4" : 392.00, "A4" : 440.00, "B4" : 493.88,
"C5" : 523.25, "D5" : 587.33, "E5" : 659.25,
"F5" : 698.46, "G5" : 783.99, "A5" : 880.00, "B5" : 987.77,
"C6" :1046.50, "E6" :1318.51,
}
# ── Normal-Akkorde (chord: 0=Am 1=G 2=F 3=Em) ─────────────────────────
const ARP := {
0: ["A4","C5","E5","C5"],
1: ["G4","B4","D5","B4"],
2: ["F4","A4","C5","A4"],
3: ["E4","G4","B4","G4"],
}
const BASS := { 0:"A3", 1:"G3", 2:"F3", 3:"E3" }
# ── Boss-Akkorde (chord: 0=Am 1=Em) ─────────────────────────────────────
const BOSS_ARP := {
0: ["A4","E5","A5","E5"], # Am — Power-Arpeggio
1: ["E4","B4","E5","B4"], # Em — Moll-Arpeggio
}
const BOSS_BASS := { 0:"A2", 1:"E2" } # tiefer Sub-Bass
# ── Boss2-Akkorde (chord: 0=E 1=F 2=Dm) — E-Phrygisch ──────────────────
const BOSS2_ARP := {
0: ["E4","B4","E5","G4"], # E (phrygisch, mit kleiner Terz G)
1: ["F4","C5","F5","A4"], # F — der phrygische II-Akkord (F→E ist der Killer)
2: ["D4","A4","D5","F4"], # Dm — dunkler iv-Akkord
}
const BOSS2_BASS := { 0:"E2", 1:"F2", 2:"D2" } # Drone-Bass Grundtöne
# ═══════════════════════════════════════════════════════════════════════════
# NORMAL-SCORE — "STELLAR DRIFT"
# Format: [mel_note, beats, chord_idx, section]
# section 0=Intro 1=Build 2=Main
# ═══════════════════════════════════════════════════════════════════════════
const SCORE: Array = [
# ═══ INTRO ═══
["A4", 2.0, 0, 0], ["E5", 2.0, 0, 0],
["C5", 2.0, 0, 0], ["A4", 2.0, 0, 0],
["E5", 1.0, 0, 0], ["A5", 1.0, 0, 0], ["E5", 1.0, 0, 0], ["C5", 1.0, 0, 0],
["A4", 4.0, 0, 0],
# ═══ BUILD ═══
["A4",0.5,0,1],["C5",0.25,0,1],["E5",0.25,0,1],
["A5",0.5,0,1],["G5",0.25,0,1],["E5",0.25,0,1],
["D5",0.5,0,1],["C5",0.25,0,1],["A4",0.25,0,1],
["C5",0.5,0,1],["R", 0.5, 0,1],
["C5",0.5,0,1],["E5",0.25,0,1],["A5",0.25,0,1],
["C6",0.5,0,1],["A5",0.25,0,1],["E5",0.25,0,1],
["G5",0.5,0,1],["E5",0.25,0,1],["C5",0.25,0,1],["A4",1.0,0,1],
["G4",0.5,1,1],["B4",0.25,1,1],["D5",0.25,1,1],
["G5",0.5,1,1],["D5",0.25,1,1],["B4",0.25,1,1],
["G4",0.5,1,1],["R", 0.25,1,1],["B4",0.25,1,1],
["D5",0.5,1,1],["G5",0.5,1,1],
["F4",0.5,2,1],["A4",0.25,2,1],["C5",0.25,2,1],
["F5",0.5,2,1],["C5",0.25,2,1],["A4",0.25,2,1],
["F5",0.5,2,1],["A5",0.25,2,1],["C6",0.25,2,1],
["A5",0.5,2,1],["F5",0.5, 2,1],
["E5",0.5,3,1],["G5",0.25,3,1],["B5",0.25,3,1],
["E6",0.5,3,1],["B5",0.25,3,1],["G5",0.25,3,1],
["A5",0.5,3,1],["G5",0.25,3,1],["E5",0.25,3,1],["B4",1.0,3,1],
# ═══ MAIN ═══
["A4",0.5,0,2],["C5",0.25,0,2],["E5",0.25,0,2],
["A5",0.5,0,2],["G5",0.25,0,2],["E5",0.25,0,2],
["D5",0.5,0,2],["C5",0.25,0,2],["A4",0.25,0,2],
["C5",0.5,0,2],["R", 0.5, 0,2],
["C5",0.5,0,2],["E5",0.25,0,2],["A5",0.25,0,2],
["C6",0.5,0,2],["A5",0.25,0,2],["E5",0.25,0,2],
["G5",0.5,0,2],["E5",0.25,0,2],["C5",0.25,0,2],["A4",1.0,0,2],
["G4",0.5,1,2],["B4",0.25,1,2],["D5",0.25,1,2],
["G5",0.5,1,2],["D5",0.25,1,2],["B4",0.25,1,2],
["G4",0.5,1,2],["R", 0.25,1,2],["B4",0.25,1,2],
["D5",0.5,1,2],["G5",0.5,1,2],
["F4",0.5,2,2],["A4",0.25,2,2],["C5",0.25,2,2],
["F5",0.5,2,2],["C5",0.25,2,2],["A4",0.25,2,2],
["F5",0.5,2,2],["A5",0.25,2,2],["C6",0.25,2,2],
["A5",0.5,2,2],["F5",0.5, 2,2],
["E5",0.5,3,2],["G5",0.25,3,2],["B5",0.25,3,2],
["E6",0.5,3,2],["B5",0.25,3,2],["G5",0.25,3,2],
["A5",0.5,3,2],["G5",0.25,3,2],["E5",0.25,3,2],["B4",1.0,3,2],
]
const LOOP_START := 9 # Loop ab BUILD (Intro überspringen)
# ═══════════════════════════════════════════════════════════════════════════
# BOSS-SCORE — "CRITICAL MASS"
# Format: [mel_note, beats, chord_idx, section]
# chord 0=Am 1=Em · section=3 (volle Intensität immer)
# BPM=175, Stil: Super Hexagon — stakkato, getrieben, 4-Bar-Loop
# ═══════════════════════════════════════════════════════════════════════════
const BOSS_SCORE: Array = [
# ══ Bar 1 (Am) — Aufstiegs-Stabs ══
["E5", 0.25, 0, 3], ["R", 0.25, 0, 3], ["G5", 0.25, 0, 3], ["A5", 0.25, 0, 3],
["E5", 0.5, 0, 3], ["R", 0.25, 0, 3], ["C5", 0.25, 0, 3],
["E5", 0.25, 0, 3], ["R", 0.25, 0, 3], ["A4", 0.5, 0, 3],
["R", 1.0, 0, 3],
# ══ Bar 2 (Am) — Synkopierter Lauf ══
["G5", 0.25, 0, 3], ["A5", 0.25, 0, 3], ["G5", 0.25, 0, 3], ["E5", 0.25, 0, 3],
["C5", 0.25, 0, 3], ["E5", 0.25, 0, 3], ["G5", 0.5, 0, 3],
["A5", 0.25, 0, 3], ["R", 0.25, 0, 3], ["G5", 0.25, 0, 3], ["E5", 0.25, 0, 3],
["C5", 0.5, 0, 3], ["R", 0.5, 0, 3],
# ══ Bar 3 (Em) — Dunkle Dringlichkeit ══
["E5", 0.25, 1, 3], ["R", 0.25, 1, 3], ["B4", 0.25, 1, 3], ["R", 0.25, 1, 3],
["G5", 0.25, 1, 3], ["E5", 0.25, 1, 3], ["D5", 0.5, 1, 3],
["B4", 0.25, 1, 3], ["R", 0.25, 1, 3], ["G5", 0.5, 1, 3],
["E5", 0.25, 1, 3], ["D5", 0.25, 1, 3], ["B4", 0.5, 1, 3],
# ══ Bar 4 (Em) — Klimax ══
["G5", 0.25, 1, 3], ["B5", 0.25, 1, 3], ["A5", 0.25, 1, 3], ["G5", 0.25, 1, 3],
["E5", 0.5, 1, 3], ["R", 0.5, 1, 3],
["D5", 0.25, 1, 3], ["R", 0.25, 1, 3], ["B4", 0.25, 1, 3], ["R", 0.25, 1, 3],
["E5", 1.0, 1, 3],
]
const BOSS_LOOP_START := 0 # Voller Loop, kein Intro
# ═══════════════════════════════════════════════════════════════════════════
# BOSS2-SCORE — "EVENT HORIZON"
# Format: [mel_note, beats, chord_idx, section]
# chord 0=E 1=F 2=Dm · section=3 (Bass+Drums immer aktiv)
# BPM=140 · E-Phrygisch · 16-Bar-Struktur: 8 Bars Phase 1 + 8 Bars Phase 2
# Melodie: lange, sparsame Töne in Phase 1 — verdichtet sich in Phase 2
# ═══════════════════════════════════════════════════════════════════════════
const BOSS2_SCORE: Array = [
# ══ PHASE 1 — Bars 18 (Index 0–14) ═══════════════════════════════════
# Bar 1 (E) — langer Tonika-Drone
["E5", 3.0, 0, 3], ["R", 1.0, 0, 3],
# Bar 2 (E) — leichte Bewegung
["G5", 2.0, 0, 3], ["E5", 2.0, 0, 3],
# Bar 3 (F) — phrygische Spannung bricht ein
["F5", 3.0, 1, 3], ["C5", 1.0, 1, 3],
# Bar 4 (F) — hält die Spannung
["F5", 2.0, 1, 3], ["A5", 2.0, 1, 3],
# Bar 5 (Dm) — dunkler Abstieg
["D5", 2.0, 2, 3], ["F5", 2.0, 2, 3],
# Bar 6 (Dm) — Weite, Atem
["A5", 3.0, 2, 3], ["R", 1.0, 2, 3],
# Bar 7 (E) — Rückkehr
["B4", 2.0, 0, 3], ["E5", 2.0, 0, 3],
# Bar 8 (E) — ganze Note als Übergang
["E5", 4.0, 0, 3],
# ══ PHASE 2 — Bars 916 (Index 1547) ═════════════════════════════════
# Verdichtung: mehr Achtel, aufsteigende Linien, Klimax-Oktav
# Bar 9 (E) — Phrygisch-Lauf aufwärts
["E5", 1.0, 0, 3], ["G5", 1.0, 0, 3], ["B5", 1.0, 0, 3], ["G5", 1.0, 0, 3],
# Bar 10 (E) — Stakkato-Antwort
["E5", 0.5, 0, 3], ["R", 0.5, 0, 3], ["B4", 1.0, 0, 3], ["G5", 1.0, 0, 3], ["E5", 1.0, 0, 3],
# Bar 11 (F) — hochklettern, der F→E-Halbton beißt
["F5", 1.0, 1, 3], ["A5", 1.0, 1, 3], ["C6", 1.0, 1, 3], ["A5", 1.0, 1, 3],
# Bar 12 (F) — absteigende Variation
["F5", 0.5, 1, 3], ["R", 0.5, 1, 3], ["C5", 1.0, 1, 3], ["A5", 1.0, 1, 3], ["F5", 1.0, 1, 3],
# Bar 13 (Dm) — Dm-Arpeggio-Lick
["D5", 1.0, 2, 3], ["F5", 1.0, 2, 3], ["A5", 1.0, 2, 3], ["F5", 1.0, 2, 3],
# Bar 14 (Dm) — Dreiklangs-Dichte
["D5", 0.5, 2, 3], ["A4", 0.5, 2, 3], ["D5", 1.0, 2, 3], ["F5", 1.0, 2, 3], ["A5", 1.0, 2, 3],
# Bar 15 (E) — finaler Aufstieg
["B4", 1.0, 0, 3], ["E5", 1.0, 0, 3], ["G5", 1.0, 0, 3], ["B5", 1.0, 0, 3],
# Bar 16 (E) — Klimax-Oktavsprung E5→E4
["E5", 2.0, 0, 3], ["E4", 2.0, 0, 3],
]
const BOSS2_LOOP_START := 0 # bei Phase 1 loopt der ganze Track
const BOSS2_PHASE2_OFFSET := 15 # Index wo Bar 9 (Phase 2) beginnt
# ── Player-Zustand ────────────────────────────────────────────────────────
var _player: AudioStreamPlayer
var _generator: AudioStreamGenerator
var _playback: AudioStreamGeneratorPlayback
var _playing := false
# Modus: 0=normal, 1=WRAITH (CRITICAL MASS), 2=LEVIATHAN (EVENT HORIZON)
var _boss_mode: int = 0
var _cur_beat: float = BEAT
# Phase-2-Umschaltung (nur boss_mode=2)
var _phase2_active: bool = false
var _pending_phase2_jump: bool = false
# Melody
var _mel_idx := 0
var _mel_pos := 0
var _mel_samples := 0
var _mel_freq := 0.0
var _mel_phase := 0.0
# Arpeggio (16tel-Noten)
var _arp_step := 0
var _arp_pos := 0
var _arp_samples := 0
var _arp_freq := 0.0
var _arp_phase := 0.0
# Bass
var _bass_chord := -1
var _bass_freq := 0.0
var _bass_phase := 0.0
# Bass-Zweit-Oszillator (nur boss_mode=2, detunet für Schwebung)
var _bass2_phase := 0.0
# Globaler Zustand
var _section := 0
var _chord := 0
var _sample_g := 0
# ── Initialisierung ───────────────────────────────────────────────────────
func _ready() -> void:
var bus_idx := AudioServer.get_bus_index("Music")
if bus_idx == -1:
AudioServer.add_bus()
bus_idx = AudioServer.get_bus_count() - 1
AudioServer.set_bus_name(bus_idx, "Music")
AudioServer.set_bus_send(bus_idx, "Master")
AudioServer.set_bus_volume_db(bus_idx,
linear_to_db(Settings.music_volume) if Settings.music_volume > 0.0 else -80.0)
_generator = AudioStreamGenerator.new()
_generator.mix_rate = SAMPLE_RATE
_generator.buffer_length = BUFFER_SIZE
_player = AudioStreamPlayer.new()
_player.stream = _generator
_player.volume_db = volume_db
_player.bus = "Music"
add_child(_player)
if autoplay:
play()
func play() -> void:
_boss_mode = 0
_cur_beat = BEAT
_phase2_active = false; _pending_phase2_jump = false
_mel_idx = 0; _mel_pos = 0; _mel_phase = 0.0
_arp_step = 0; _arp_pos = 0; _arp_phase = 0.0
_bass_chord = -1; _bass_phase = 0.0; _bass2_phase = 0.0
_sample_g = 0; _section = 0; _chord = 0
_playing = true
_player.play()
_playback = _player.get_stream_playback()
_load_mel()
func play_boss() -> void:
_boss_mode = 1
_cur_beat = BOSS_BEAT
_phase2_active = false; _pending_phase2_jump = false
_mel_idx = 0; _mel_pos = 0; _mel_phase = 0.0
_arp_step = 0; _arp_pos = 0; _arp_phase = 0.0
_bass_chord = -1; _bass_phase = 0.0; _bass2_phase = 0.0
_sample_g = 0; _section = 3; _chord = 0
if not _playing:
_playing = true
_player.play()
_playback = _player.get_stream_playback()
_load_mel()
func play_boss_leviathan(phase: int = 1) -> void:
_boss_mode = 2
_cur_beat = BOSS2_BEAT
_phase2_active = (phase >= 2)
_pending_phase2_jump = false
_mel_idx = BOSS2_PHASE2_OFFSET if _phase2_active else 0
_mel_pos = 0; _mel_phase = 0.0
_arp_step = 0; _arp_pos = 0; _arp_phase = 0.0
_bass_chord = -1; _bass_phase = 0.0; _bass2_phase = 0.0
_sample_g = 0; _section = 3; _chord = 0
if not _playing:
_playing = true
_player.play()
_playback = _player.get_stream_playback()
_load_mel()
# Wird vom game_world.gd aufgerufen, wenn LEVIATHAN Phase 2 erreicht (< 50% HP).
# Setzt nur ein Flag — der Sprung geschieht beim nächsten Note-Wechsel,
# damit der Rhythmus nicht bricht.
func enter_phase2() -> void:
if _boss_mode == 2 and not _phase2_active:
_pending_phase2_jump = true
func play_normal() -> void:
if _boss_mode == 0:
return # already playing normal — don't restart
_boss_mode = 0
_cur_beat = BEAT
_phase2_active = false; _pending_phase2_jump = false
_mel_idx = LOOP_START; _mel_pos = 0; _mel_phase = 0.0
_arp_step = 0; _arp_pos = 0; _arp_phase = 0.0
_bass_chord = -1; _bass_phase = 0.0; _bass2_phase = 0.0
_sample_g = 0
_load_mel()
func stop_music() -> void:
_playing = false
_player.stop()
func set_music_volume(linear: float) -> void:
var idx := AudioServer.get_bus_index("Music")
if idx != -1:
AudioServer.set_bus_volume_db(idx,
linear_to_db(linear) if linear > 0.0 else -80.0)
func set_muted(muted: bool) -> void:
var idx := AudioServer.get_bus_index("Music")
if idx != -1:
AudioServer.set_bus_mute(idx, muted)
# ── Hauptschleife ─────────────────────────────────────────────────────────
func _process(_delta: float) -> void:
if _playing:
_fill_buffer()
func _fill_buffer() -> void:
var frames := _playback.get_frames_available()
for _i in frames:
# ── Melodie-Note weiterschalten ──────────────────────────────
if _mel_pos >= _mel_samples:
# Phase-2-Sprung bei LEVIATHAN: smooth am Note-Ende
if _pending_phase2_jump and _boss_mode == 2:
_mel_idx = BOSS2_PHASE2_OFFSET
_phase2_active = true
_pending_phase2_jump = false
else:
_mel_idx += 1
var score: Array
var loop_start: int
match _boss_mode:
2:
score = BOSS2_SCORE
# Phase-2 loopt nur die zweite Hälfte (endlose Eskalation)
loop_start = BOSS2_PHASE2_OFFSET if _phase2_active else BOSS2_LOOP_START
1:
score = BOSS_SCORE
loop_start = BOSS_LOOP_START
_:
score = SCORE
loop_start = LOOP_START
if _mel_idx >= score.size():
if loop:
_mel_idx = loop_start
else:
_playing = false
_player.stop()
return
_mel_pos = 0
_load_mel()
# ── Arp-Note weiterschalten (16tel) ──────────────────────────
if _arp_pos >= _arp_samples:
_arp_step = (_arp_step + 1) % 4
_arp_pos = 0
_load_arp()
# ── Samples mischen ──────────────────────────────────────────
var s := 0.0
# Voice 0: Melodie
# Normal: Triangle warm · Boss1 (WRAITH): Square scharf · Boss2 (LEVIATHAN): Triangle mit langem Attack
if _mel_freq > 0.0:
var attack: int
var release: int
match _boss_mode:
2: attack = 800; release = 1500
1: attack = 100; release = 500
_: attack = 200; release = 1200
var env := _env(_mel_pos, _mel_samples, attack, release)
var ph := fmod(_mel_phase, 1.0)
if _boss_mode == 1:
# Square-Wave: schärfer, beißender Klang
var sq := 1.0 if ph < 0.5 else -1.0
s += sq * env * 0.26
else:
# Triangle-Wave: warm, vibraphone-artig (normal & boss2)
var tri := 1.0 - absf(4.0 * ph - 2.0)
var mel_gain := 0.32 if _boss_mode == 2 else 0.38
s += tri * env * mel_gain
# Phase-2-Oktav-Layer: zusätzliche Triangle eine Oktave höher
if _boss_mode == 2 and _phase2_active:
var ph2 := fmod(_mel_phase * 2.0, 1.0)
var tri2 := 1.0 - absf(4.0 * ph2 - 2.0)
s += tri2 * env * 0.14
_mel_phase += _mel_freq / SAMPLE_RATE
# Voice 1: Arpeggio
# Normal/Boss1: Square 50% · Boss2: leise Triangle (Sternengeflimmer)
if _arp_freq > 0.0:
var env := _env(_arp_pos, _arp_samples, 40, 400)
var ph := fmod(_arp_phase, 1.0)
if _boss_mode == 2:
var tri := 1.0 - absf(4.0 * ph - 2.0)
var gain := 0.09 if _phase2_active else 0.06
s += tri * env * gain
else:
var sq := 1.0 if ph < 0.5 else -1.0
var gain: float
if _section == 0:
gain = 0.05
elif _boss_mode == 1:
gain = 0.20
else:
gain = 0.11
s += sq * env * gain
_arp_phase += _arp_freq / SAMPLE_RATE
# Voice 2: Bass — Sawtooth (ab section 1)
# Boss2: zusätzlicher 2. Saw-Oszillator, detunet für Drone-Schwebung
if _section >= 1 and _bass_freq > 0.0:
var ph := fmod(_bass_phase, 1.0)
var bass_gain: float
if _boss_mode == 2:
bass_gain = 0.22
elif _boss_mode == 1:
bass_gain = 0.30
elif _section == 1:
bass_gain = 0.15
else:
bass_gain = 0.25
s += (2.0 * ph - 1.0) * bass_gain
_bass_phase += _bass_freq / SAMPLE_RATE
if _boss_mode == 2:
# 2. Oszillator detunet (~3‰ = leichte Schwebung bei ~82 Hz: ~0.25 Hz)
var ph2 := fmod(_bass2_phase, 1.0)
s += (2.0 * ph2 - 1.0) * 0.22
_bass2_phase += (_bass_freq * 1.003) / SAMPLE_RATE
# Voice 3: Schlagzeug (ab section 2 oder Boss-Modus)
if _section >= 2:
if _boss_mode == 2:
# ── LEVIATHAN: Tom-lastig, keine Hi-Hats ─────────────────
# Kick auf Beat 1 jedes Bars (in Phase 2 zusätzlich auf Beat 3)
var bar_period := int(4.0 * _cur_beat * SAMPLE_RATE)
var bar_pos := _sample_g % bar_period
var kick_len := 900
if bar_pos < kick_len:
var kick_env := 1.0 - float(bar_pos) / float(kick_len)
var kick_f := 1.0 - float(bar_pos) / float(kick_len)
s += sin(kick_f * kick_f * 55.0) * kick_env * 0.26
if _phase2_active:
var beat3_offset := int(2.0 * _cur_beat * SAMPLE_RATE)
var kick2_pos := (_sample_g + bar_period - beat3_offset) % bar_period
if kick2_pos < kick_len:
var kick2_env := 1.0 - float(kick2_pos) / float(kick_len)
var kick2_f := 1.0 - float(kick2_pos) / float(kick_len)
s += sin(kick2_f * kick2_f * 55.0) * kick2_env * 0.22
# Floor-Tom auf Beats 1 & 3 (period = 2 beats, tiefer Sinus mit Pitch-Drop)
var tom_period := int(2.0 * _cur_beat * SAMPLE_RATE)
var tom_pos := _sample_g % tom_period
var tom_len := 2200
if tom_pos < tom_len:
var tom_t := float(tom_pos) / float(tom_len)
var tom_env := 1.0 - tom_t
# Pitch-Envelope: startet bei 130 Hz, fällt auf 70 Hz
var tom_freq := 130.0 - 60.0 * tom_t
var tom_ph := float(tom_pos) / float(SAMPLE_RATE) * tom_freq
s += sin(tom_ph * TAU) * tom_env * 0.18
# Mid-Tom-Fill: Beat 4 jedes 4. Bars (= Ende jeder 4-Bar-Phrase)
var phrase_period := int(16.0 * _cur_beat * SAMPLE_RATE)
var phrase_pos := _sample_g % phrase_period
var mid_tom_start := int(15.0 * _cur_beat * SAMPLE_RATE)
var mid_tom_rel := phrase_pos - mid_tom_start
if mid_tom_rel >= 0:
# 4 schnelle Mid-Tom-Hits auf dem letzten Beat
var hit_len: int = int(_cur_beat * SAMPLE_RATE * 0.25)
@warning_ignore("integer_division")
var hit_idx: int = mid_tom_rel / hit_len
if hit_idx < 4:
var hit_pos := mid_tom_rel % hit_len
if hit_pos < 1400:
var mt_t := float(hit_pos) / 1400.0
var mt_env := 1.0 - mt_t
var mt_freq := 220.0 - 80.0 * mt_t
var mt_ph := float(hit_pos) / float(SAMPLE_RATE) * mt_freq
s += sin(mt_ph * TAU) * mt_env * 0.14
# Reverse-Whoosh alle 8 Bars: aufsteigende Noise-Envelope als Übergang
var whoosh_period := int(32.0 * _cur_beat * SAMPLE_RATE)
var whoosh_pos := _sample_g % whoosh_period
var whoosh_start := whoosh_period - int(2.0 * _cur_beat * SAMPLE_RATE)
if whoosh_pos >= whoosh_start:
var w_t := float(whoosh_pos - whoosh_start) / float(whoosh_period - whoosh_start)
var w_env := w_t * w_t # aufsteigend
s += randf_range(-1.0, 1.0) * w_env * 0.09
else:
# ── Normal & WRAITH: Hi-Hats + Kick + (Boss1) Snare ──────
var hat_beat_frac := 0.25 if _boss_mode == 1 else 0.5
var hat_period := int(hat_beat_frac * _cur_beat * SAMPLE_RATE)
var hat_pos := _sample_g % hat_period
var hat_len := 200 if _boss_mode == 1 else 600
if hat_pos < hat_len:
var hat_env := 1.0 - float(hat_pos) / float(hat_len)
var hat_vol := 0.08 if _boss_mode == 1 else 0.07
s += randf_range(-1.0, 1.0) * hat_env * hat_vol
var kick_period := int(_cur_beat * SAMPLE_RATE)
var kick_pos := _sample_g % kick_period
var kick_len := 750 if _boss_mode == 1 else 800
if kick_pos < kick_len:
var kick_env := 1.0 - float(kick_pos) / float(kick_len)
var kick_f := 1.0 - float(kick_pos) / float(kick_len)
var kick_vol := 0.22 if _boss_mode == 1 else 0.18
s += sin(kick_f * kick_f * 60.0) * kick_env * kick_vol
if _boss_mode == 1:
var snare_period := int(2.0 * _cur_beat * SAMPLE_RATE)
var snare_offset := int(1.0 * _cur_beat * SAMPLE_RATE)
var snare_pos := (_sample_g + snare_offset) % snare_period
if snare_pos < 340:
var snare_env := 1.0 - float(snare_pos) / 340.0
s += randf_range(-1.0, 1.0) * snare_env * 0.13
_playback.push_frame(Vector2(s, s))
_mel_pos += 1
_arp_pos += 1
_sample_g += 1
# ── Note laden ────────────────────────────────────────────────────────────
func _load_mel() -> void:
var score: Array
match _boss_mode:
2: score = BOSS2_SCORE
1: score = BOSS_SCORE
_: score = SCORE
var e: Array = score[_mel_idx]
_mel_freq = FREQ.get(e[0], 0.0)
_mel_samples = int(float(e[1]) * _cur_beat * SAMPLE_RATE)
_mel_phase = 0.0
_section = e[3]
if e[2] != _chord:
_chord = e[2]
_arp_step = 0
_arp_pos = 0
_load_arp()
if e[2] != _bass_chord:
_bass_chord = e[2]
var bass_dict: Dictionary
match _boss_mode:
2: bass_dict = BOSS2_BASS
1: bass_dict = BOSS_BASS
_: bass_dict = BASS
_bass_freq = FREQ.get(bass_dict[_bass_chord], 0.0)
_bass_phase = 0.0
_bass2_phase = 0.0
func _load_arp() -> void:
var arp_dict: Dictionary
var arp_step_count: int = 4
match _boss_mode:
2:
arp_dict = BOSS2_ARP
# Arp-Geschwindigkeit: Phase 1 = 8tel, Phase 2 = 16tel (verdichtet)
var arp_frac := 0.25 if _phase2_active else 0.5
_arp_samples = int(arp_frac * _cur_beat * SAMPLE_RATE)
1:
arp_dict = BOSS_ARP
_arp_samples = int(0.25 * _cur_beat * SAMPLE_RATE)
_:
arp_dict = ARP
_arp_samples = int(0.25 * _cur_beat * SAMPLE_RATE)
_arp_freq = FREQ.get(arp_dict[_chord][_arp_step % arp_step_count], 0.0)
_arp_phase = 0.0
# ── Hüllkurve ─────────────────────────────────────────────────────────────
func _env(pos: int, total: int, attack: int, release: int) -> float:
if pos < attack:
return float(pos) / maxf(attack, 1.0)
elif pos > total - release:
return float(total - pos) / maxf(release, 1.0)
return 1.0