diff --git a/src/app/corne/melon-game.tsx b/src/app/corne/melon-game.tsx index c651384..bbfcfe7 100644 --- a/src/app/corne/melon-game.tsx +++ b/src/app/corne/melon-game.tsx @@ -16,6 +16,7 @@ import { MAX_FRAME_DT_MS, WALL_THICKNESS, } from "@/lib/melon/constants"; +import { playMergeSound } from "@/lib/melon/sound"; import type { MemberRow } from "./page"; type Status = "playing" | "dead"; @@ -329,10 +330,12 @@ export function MelonGame({ const newTier = merge.tier + 1; scoreRef.current += newTier; spawned.push(spawnPiece(newTier, merge.x, merge.y)); + playMergeSound(newTier, maxTier); } else { // Palier maximum déjà atteint : pas de pièce plus grande, juste // un bonus de score pour la fusion finale. scoreRef.current += maxTier * 2; + playMergeSound(maxTier, maxTier); } } diff --git a/src/lib/melon/sound.ts b/src/lib/melon/sound.ts new file mode 100644 index 0000000..43bd7f1 --- /dev/null +++ b/src/lib/melon/sound.ts @@ -0,0 +1,50 @@ +// Petit "pop" synthétisé via Web Audio (pas de fichier audio dans le +// projet, cohérent avec le reste de l'app qui dessine tout plutôt que de +// dépendre d'assets externes — voir les ornements SVG/canvas ailleurs). +// AudioContext créé paresseusement au premier appel : le construire avant +// une interaction utilisateur déclenche des avertissements navigateur +// (autoplay policy) — ici le premier merge n'arrive jamais avant un +// premier lâcher, donc toujours après un vrai geste utilisateur. +let audioContext: AudioContext | null = null; + +function getAudioContext(): AudioContext | null { + if (typeof window === "undefined") return null; + if (!audioContext) { + const Ctor = + window.AudioContext ?? + (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext; + if (!Ctor) return null; + audioContext = new Ctor(); + } + if (audioContext.state === "suspended") { + audioContext.resume().catch(() => {}); + } + return audioContext; +} + +// Fréquence plus grave à mesure que le palier de la pièce issue de la +// fusion monte : deux petites pièces qui fusionnent ne doit pas "sonner" +// comme deux grosses. +export function playMergeSound(tier: number, maxTier: number): void { + const ctx = getAudioContext(); + if (!ctx) return; + + const progress = maxTier > 0 ? Math.min(1, Math.max(0, tier / maxTier)) : 0; + const baseFreq = 720 - progress * 380; + const now = ctx.currentTime; + + const oscillator = ctx.createOscillator(); + const gain = ctx.createGain(); + oscillator.type = "sine"; + oscillator.frequency.setValueAtTime(baseFreq, now); + oscillator.frequency.exponentialRampToValueAtTime(baseFreq * 1.6, now + 0.08); + + gain.gain.setValueAtTime(0.0001, now); + gain.gain.exponentialRampToValueAtTime(0.25, now + 0.01); + gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.18); + + oscillator.connect(gain); + gain.connect(ctx.destination); + oscillator.start(now); + oscillator.stop(now + 0.2); +}