Sensory-friendly: soft sine sounds, pentatonic ball tones, gentle errors, Zen mode

This commit is contained in:
enne2
2026-08-10 19:35:10 +02:00
parent e20482676f
commit f9837153a0
3 changed files with 86 additions and 25 deletions
+52 -24
View File
@@ -29,6 +29,16 @@ const COLORS := {
"viola": Color("9b59b6"),
}
# Note pentatoniche (Do4 Re4 Mi4 Sol4 La4): qualsiasi pallina toccata
# produce sempre un suono armonico e piacevole (sensory-friendly).
const BALL_NOTES := {
"rosso": 261.63,
"blu": 293.66,
"verde": 329.63,
"giallo": 392.00,
"viola": 440.00,
}
var balls := {} # color_name -> Ball
var target_color := "" # bersaglio corrente (vuoto = nessun bersaglio)
var score := 0
@@ -51,6 +61,7 @@ var sound_launch: AudioStreamWAV
var sound_error: AudioStreamWAV
var sound_timeout: AudioStreamWAV
var sound_milestone: AudioStreamWAV
var ball_sounds := {} # color_name -> AudioStreamWAV (tono pentatonico)
var bgm: AudioStreamPlayer
var options_menu: CanvasLayer
@@ -70,6 +81,8 @@ func _process(delta: float) -> void:
elapsed += delta
if target_color == "":
return # in attesa che una pallina torni a casa: nessun countdown
if Settings.zen_mode:
return # modalità zen: nessun countdown né decay (niente pressione)
# il combo decade in proporzione al tempo che aspetto prima di cliccare
if combo > 0.0:
combo = maxf(0.0, combo - _combo_decay() * delta)
@@ -214,10 +227,15 @@ func _build_ui() -> void:
# ------------------------------------------------------------------- audio
func _init_audio() -> void:
sound_launch = _create_tone(587.33, 0.12, "sine") # Re5
sound_error = _create_tone(164.81, 0.18, "square") # Mi3 basso
sound_timeout = _create_tone(220.0, 0.22, "saw") # La3
sound_milestone = _create_arpeggio([523.25, 659.25, 783.99, 1046.5], 0.12) # Do5 Mi5 Sol5 Do6
# Suoni MORBIDI (sine, inviluppo dolce) per non sovrastimolare i bambini
# neurodivergenti: niente onde quadre/sega, volumi attenuati.
sound_launch = _create_tone(660.0, 0.18, 0.3) # marimba morbido
sound_error = _create_tone(220.0, 0.15, 0.16) # "pop" basso e attenuato
sound_timeout = _create_tone(330.0, 0.2, 0.16) # tono dolce
sound_milestone = _create_soft_arpeggio([523.25, 659.25, 783.99, 1046.5], 0.12)
# tono pentatonico per ogni pallina colorata
for cn in BALL_NOTES:
ball_sounds[cn] = _create_tone(BALL_NOTES[cn], 0.2, 0.3)
func _setup_bgm() -> void:
@@ -236,7 +254,7 @@ func _setup_bgm() -> void:
bgm.play()
func _create_arpeggio(freqs: Array, note_dur: float = 0.12) -> AudioStreamWAV:
func _create_soft_arpeggio(freqs: Array, note_dur: float = 0.12) -> AudioStreamWAV:
var sample_rate := 22050
var total := 0
for f in freqs:
@@ -244,13 +262,17 @@ func _create_arpeggio(freqs: Array, note_dur: float = 0.12) -> AudioStreamWAV:
var data := PackedByteArray()
data.resize(total)
var idx := 0
var attack := int(sample_rate * 0.01)
for f in freqs:
var n := int(sample_rate * note_dur)
for i in n:
var t := float(i) / sample_rate
var env := 1.0 - (float(i) / n)
var env := 1.0
if i < attack:
env = float(i) / attack
env *= exp(-3.0 * t / note_dur)
var s := sin(t * f * TAU) * env
var val := int(clampf(s * 0.35 * 127.0, -128.0, 127.0))
var val := int(clampf(s * 0.3 * 127.0, -128.0, 127.0))
data[idx] = val & 0xFF
idx += 1
var stream := AudioStreamWAV.new()
@@ -260,22 +282,22 @@ func _create_arpeggio(freqs: Array, note_dur: float = 0.12) -> AudioStreamWAV:
return stream
func _create_tone(freq: float, duration: float, wave_type: String = "sine") -> AudioStreamWAV:
func _create_tone(freq: float, duration: float, amp: float = 0.3) -> AudioStreamWAV:
# Onda sinusoidale pura con attacco morbido e decadimento esponenziale:
# timbro dolce, senza clic né armoniche spigolose.
var sample_rate := 22050
var num_samples := int(sample_rate * duration)
var data := PackedByteArray()
data.resize(num_samples)
var attack := int(sample_rate * 0.01)
for i in num_samples:
var t := float(i) / sample_rate
var env := 1.0 - (float(i) / num_samples)
var s := 0.0
if wave_type == "sine":
s = sin(t * freq * TAU)
elif wave_type == "square":
s = 1.0 if sin(t * freq * TAU) > 0.0 else -1.0
elif wave_type == "saw":
s = fmod(t * freq, 1.0) * 2.0 - 1.0
var val := int(clampf(s * env * 0.35 * 127.0, -128.0, 127.0))
var env := 1.0
if i < attack:
env = float(i) / attack
env *= exp(-3.0 * t / duration)
var s := sin(t * freq * TAU)
var val := int(clampf(s * env * amp * 127.0, -128.0, 127.0))
data[i] = val & 0xFF
var stream := AudioStreamWAV.new()
stream.format = AudioStreamWAV.FORMAT_8_BITS
@@ -324,10 +346,10 @@ func _handle_click(pos: Vector2) -> void:
return # click su spazio vuoto
if clicked.color_name == target_color:
# corretto: suono lancio, punti, nuovo bersaglio, reset timer
combo += 1.0
# corretto: suono pentatonico della pallina, punti, nuovo bersaglio
combo = minf(float(Settings.max_combo), combo + 1.0)
score += _points_for_combo()
_play_sound(sound_launch)
_play_sound(ball_sounds.get(clicked.color_name, sound_launch))
var mt := _current_time()
clicked.launch(mt)
window_time = mt
@@ -336,16 +358,22 @@ func _handle_click(pos: Vector2) -> void:
_update_hud()
_check_milestone()
else:
# tasto sbagliato: suono errore, reset combo, nessun game over
# tasto sbagliato: suono morbido, penalità gentile (combo -1) o reset
_play_sound(sound_error)
combo = 0.0
if Settings.gentle_errors:
combo = maxf(0.0, combo - 1.0)
else:
combo = 0.0
_update_hud()
func _on_timeout() -> void:
# tempo scaduto: suono timeout, reset combo, nuovo bersaglio (se possibile), nessun game over
# tempo scaduto: suono dolce, penalità gentile, nuovo bersaglio, nessun game over
_play_sound(sound_timeout)
combo = 0.0
if Settings.gentle_errors:
combo = maxf(0.0, combo - 1.0)
else:
combo = 0.0
window_timer = 0.0
_pick_target()
_update_hud()