Refactor Godot architecture into AppShell, modular menus and game domains

This commit is contained in:
enne2
2026-09-06 17:07:05 +02:00
parent a73fb4b55f
commit f4a9da6bc3
209 changed files with 2726 additions and 2072 deletions
@@ -0,0 +1,138 @@
extends RefCounted
## Genera proceduralmente un loop musicale da circo (valzer calliope) in WAV.
## Nessun file audio esterno: tutto sintetizzato a runtime, con loop seamless.
const SR := 22050
static func generate() -> AudioStreamWAV:
var bpm := 132.0
var beat := 60.0 / bpm
var bars := 8
var total_beats := bars * 3
var total_seconds := total_beats * beat
var n := int(SR * total_seconds)
var mix := PackedFloat32Array()
mix.resize(n)
# Melodia (calliope) — 8 battute in 3/4
var melody := [
[72, 1.0], [76, 1.0], [79, 1.0],
[72, 1.0], [76, 1.0], [79, 1.0],
[69, 1.0], [72, 1.0], [76, 1.0],
[69, 1.0], [72, 1.0], [76, 1.0],
[65, 1.0], [69, 1.0], [72, 1.0],
[65, 1.0], [69, 1.0], [72, 1.0],
[67, 1.0], [71, 1.0], [74, 1.0],
[72, 2.0], [0, 1.0],
]
# Accompagnamento oom-pah-pah: [basso, [accordo]]
var chords := [
[48, [60, 64, 67]],
[48, [60, 64, 67]],
[45, [57, 60, 64]],
[45, [57, 60, 64]],
[41, [53, 57, 60]],
[41, [53, 57, 60]],
[43, [55, 59, 62]],
[48, [60, 64, 67]],
]
# Melodia
var t := 0.0
for note in melody:
var midi: int = note[0]
var beats: float = note[1]
var dur := beats * beat
if midi > 0:
_add_note(mix, t, dur, _freq(midi), 0.5, "calliope")
t += dur
# Accompagnamento
t = 0.0
for bar in bars:
var bass: int = chords[bar][0]
var chord: Array = chords[bar][1]
_add_note(mix, t, beat * 0.9, _freq(bass), 0.42, "bass")
for b in [1, 2]:
var ct: float = t + b * beat
for m in chord:
_add_note(mix, ct, beat * 0.8, _freq(int(m)), 0.16, "chord")
t += 3.0 * beat
# Fade in/out (30ms) per loop seamless senza click
var fade := int(SR * 0.03)
for i in fade:
var g := float(i) / fade
mix[i] *= g
mix[n - 1 - i] *= g
# Normalizzazione
var peak := 0.0
for i in n:
var a := absf(mix[i])
if a > peak:
peak = a
var norm := 1.0
if peak > 0.0:
norm = 0.9 / peak
var data := PackedByteArray()
data.resize(n)
for i in n:
var v := int(clampf(mix[i] * norm * 127.0, -128.0, 127.0))
data[i] = v & 0xFF
var stream := AudioStreamWAV.new()
stream.format = AudioStreamWAV.FORMAT_8_BITS
stream.mix_rate = SR
stream.data = data
stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
stream.loop_begin = 0
stream.loop_end = n
return stream
static func _freq(midi: int) -> float:
return 440.0 * pow(2.0, (midi - 69) / 12.0)
static func _square(t: float, f: float) -> float:
return 1.0 if sin(t * f * TAU) > 0.0 else -1.0
static func _saw(t: float, f: float) -> float:
return fmod(t * f, 1.0) * 2.0 - 1.0
static func _envelope(t: float, dur: float) -> float:
var a := 0.02
var r := 0.06
var e := 1.0
if t < a:
e = t / a
var rem := dur - t
if rem < r:
e = minf(e, rem / r)
return e
static func _add_note(mix: PackedFloat32Array, start: float, dur: float, freq: float, amp: float, kind: String) -> void:
var start_i := int(start * SR)
var cnt := int(dur * SR)
for i in cnt:
var idx := start_i + i
if idx < 0 or idx >= mix.size():
break
var t := float(i) / SR
var env := _envelope(t, dur)
var s := 0.0
if kind == "calliope":
var vib := 1.0 + 0.006 * sin(t * 2.0 * PI * 5.0)
var f := freq * vib
s = 0.5 * _square(t, f) + 0.3 * _saw(t, f * 1.004) + 0.2 * _square(t, f * 0.996)
elif kind == "bass":
s = 0.6 * _saw(t, freq) + 0.4 * _square(t, freq)
elif kind == "chord":
s = 0.5 * _square(t, freq) + 0.5 * _saw(t, freq)
mix[idx] += s * env * amp
@@ -0,0 +1 @@
uid://xlg6u5nv5rue
@@ -0,0 +1,58 @@
class_name JuggleBoardAudio
extends Node
## Audio e BGM del JuggleBoard. Nessuna logica di gameplay.
const BGM_STREAM := preload("res://games/juggleboard/assets/audio/circus.ogg")
const MUSIC_FALLBACK := preload("res://games/juggleboard/scripts/audio/circus_music.gd")
var launch: AudioStreamWAV
var error: AudioStreamWAV
var timeout: AudioStreamWAV
var milestone: AudioStreamWAV
var victory: AudioStreamWAV
var defeat: AudioStreamWAV
var lose_heart: AudioStreamWAV
var ball_tones: Dictionary = {}
var bgm: AudioStreamPlayer
func _ready() -> void:
_build_effects()
_build_bgm()
func _build_effects() -> void:
launch = AudioFactory.create_tone(660.0, 0.18, 0.3)
error = AudioFactory.create_tone(220.0, 0.15, 0.16)
timeout = AudioFactory.create_tone(330.0, 0.2, 0.16)
milestone = AudioFactory.create_arpeggio([523.25, 659.25, 783.99, 1046.5], 0.12)
victory = AudioFactory.create_arpeggio([523.25, 659.25, 783.99, 1046.5, 1318.51, 1567.98], 0.16)
defeat = AudioFactory.create_arpeggio([392.0, 329.63, 261.63], 0.18)
lose_heart = AudioFactory.create_tone(330.0, 0.15, 0.2)
for color_name in JuggleBoardConfig.BALL_NOTES:
ball_tones[color_name] = AudioFactory.create_tone(JuggleBoardConfig.BALL_NOTES[color_name], 0.2, 0.3)
func _build_bgm() -> void:
bgm = AudioStreamPlayer.new()
bgm.stream = BGM_STREAM if BGM_STREAM else MUSIC_FALLBACK.generate()
bgm.volume_db = -14.0
bgm.process_mode = Node.PROCESS_MODE_ALWAYS
add_child(bgm)
func play(stream: AudioStream) -> void:
if stream:
AudioFactory.play_once(self, stream)
func play_ball(color_name: String) -> void:
play(ball_tones.get(color_name, launch))
func start_music() -> void:
bgm.play()
func stop_music() -> void:
bgm.stop()
@@ -0,0 +1 @@
uid://dsywfqv2xa0vx