Ajoute un son de fusion dans La Corne d'Abondance
Build and deploy / deploy (push) Successful in 36s

Pas de fichier audio dans le projet, donc un petit "pop" synthétisé via Web Audio (src/lib/melon/sound.ts), joué à chaque fusion. La hauteur baisse avec le palier de la pièce obtenue, pour que fusionner deux grosses pièces sonne différemment de deux petites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Valentin ROBIN
2026-08-25 17:42:04 +02:00
parent 5e75939b94
commit ede1d6c74d
2 changed files with 53 additions and 0 deletions
+3
View File
@@ -16,6 +16,7 @@ import {
MAX_FRAME_DT_MS, MAX_FRAME_DT_MS,
WALL_THICKNESS, WALL_THICKNESS,
} from "@/lib/melon/constants"; } from "@/lib/melon/constants";
import { playMergeSound } from "@/lib/melon/sound";
import type { MemberRow } from "./page"; import type { MemberRow } from "./page";
type Status = "playing" | "dead"; type Status = "playing" | "dead";
@@ -329,10 +330,12 @@ export function MelonGame({
const newTier = merge.tier + 1; const newTier = merge.tier + 1;
scoreRef.current += newTier; scoreRef.current += newTier;
spawned.push(spawnPiece(newTier, merge.x, merge.y)); spawned.push(spawnPiece(newTier, merge.x, merge.y));
playMergeSound(newTier, maxTier);
} else { } else {
// Palier maximum déjà atteint : pas de pièce plus grande, juste // Palier maximum déjà atteint : pas de pièce plus grande, juste
// un bonus de score pour la fusion finale. // un bonus de score pour la fusion finale.
scoreRef.current += maxTier * 2; scoreRef.current += maxTier * 2;
playMergeSound(maxTier, maxTier);
} }
} }
+50
View File
@@ -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);
}