sound at game mode

This commit is contained in:
polwex 2026-03-24 16:24:45 +07:00
parent d42e47b15b
commit 0734364269
2 changed files with 135 additions and 1 deletions

View file

@ -1,5 +1,6 @@
import { useState, useEffect, useRef, useCallback } from 'react'
import styles from '../styles/game.module.css'
import { playKeyClick, playWordComplete, playCombo, playMiss, playGameOver } from '../sounds'
const WORD_POOL = [
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'her',
@ -152,6 +153,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
}
}
if (lostLife) {
playMiss()
setLives(l => {
const nl = l - 1
if (nl <= 0) {
@ -176,6 +178,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
// Fire onGameOver
useEffect(() => {
if (gameOver && started) {
playGameOver()
onGameOver(score)
}
}, [gameOver, started, score, onGameOver])
@ -204,6 +207,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
if (e.key.length !== 1) return
e.preventDefault()
playKeyClick()
const newInput = inputRef.current + e.key
@ -214,6 +218,7 @@ export function GameMode({ highScore, onGameOver }: Props) {
const points = completed.text.length * 10
setScore(s => s + points)
updateInput('')
playWordComplete()
// Spawn explosion at word position
const now = Date.now()
@ -231,7 +236,10 @@ export function GameMode({ highScore, onGameOver }: Props) {
// Combo tracking
setCombo(c => {
const next = c + 1
if (next >= 3) setShowCombo(now)
if (next >= 3) {
setShowCombo(now)
playCombo()
}
return next
})
clearTimeout(comboTimerRef.current)

126
src/sounds.ts Normal file
View file

@ -0,0 +1,126 @@
// Synthesized game sounds using Web Audio API — no audio files needed
let ctx: AudioContext | null = null
function getCtx(): AudioContext {
if (!ctx) ctx = new AudioContext()
if (ctx.state === 'suspended') ctx.resume()
return ctx
}
export function playKeyClick() {
const c = getCtx()
const osc = c.createOscillator()
const gain = c.createGain()
osc.type = 'square'
osc.frequency.setValueAtTime(800, c.currentTime)
osc.frequency.exponentialRampToValueAtTime(600, c.currentTime + 0.04)
gain.gain.setValueAtTime(0.06, c.currentTime)
gain.gain.exponentialRampToValueAtTime(0.001, c.currentTime + 0.04)
osc.connect(gain).connect(c.destination)
osc.start()
osc.stop(c.currentTime + 0.04)
}
export function playWordComplete() {
const c = getCtx()
const t = c.currentTime
// Bright rising arpeggio
const notes = [523, 659, 784, 1047] // C5 E5 G5 C6
notes.forEach((freq, i) => {
const osc = c.createOscillator()
const gain = c.createGain()
osc.type = 'sine'
osc.frequency.setValueAtTime(freq, t + i * 0.06)
gain.gain.setValueAtTime(0, t)
gain.gain.linearRampToValueAtTime(0.15, t + i * 0.06)
gain.gain.exponentialRampToValueAtTime(0.001, t + i * 0.06 + 0.2)
osc.connect(gain).connect(c.destination)
osc.start(t + i * 0.06)
osc.stop(t + i * 0.06 + 0.2)
})
}
export function playCombo() {
const c = getCtx()
const t = c.currentTime
// Power-up sweep
const osc = c.createOscillator()
const gain = c.createGain()
osc.type = 'sawtooth'
osc.frequency.setValueAtTime(300, t)
osc.frequency.exponentialRampToValueAtTime(1200, t + 0.15)
osc.frequency.exponentialRampToValueAtTime(800, t + 0.3)
gain.gain.setValueAtTime(0.1, t)
gain.gain.linearRampToValueAtTime(0.15, t + 0.1)
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.35)
osc.connect(gain).connect(c.destination)
osc.start(t)
osc.stop(t + 0.35)
// Sparkle overtone
const osc2 = c.createOscillator()
const gain2 = c.createGain()
osc2.type = 'sine'
osc2.frequency.setValueAtTime(1600, t + 0.05)
osc2.frequency.exponentialRampToValueAtTime(2400, t + 0.2)
gain2.gain.setValueAtTime(0.08, t + 0.05)
gain2.gain.exponentialRampToValueAtTime(0.001, t + 0.35)
osc2.connect(gain2).connect(c.destination)
osc2.start(t + 0.05)
osc2.stop(t + 0.35)
}
export function playMiss() {
const c = getCtx()
const t = c.currentTime
// Low thud
const osc = c.createOscillator()
const gain = c.createGain()
osc.type = 'sine'
osc.frequency.setValueAtTime(150, t)
osc.frequency.exponentialRampToValueAtTime(60, t + 0.2)
gain.gain.setValueAtTime(0.2, t)
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.25)
osc.connect(gain).connect(c.destination)
osc.start(t)
osc.stop(t + 0.25)
// Noise burst
const bufferSize = c.sampleRate * 0.1
const buffer = c.createBuffer(1, bufferSize, c.sampleRate)
const data = buffer.getChannelData(0)
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize)
}
const noise = c.createBufferSource()
const noiseGain = c.createGain()
noise.buffer = buffer
noiseGain.gain.setValueAtTime(0.08, t)
noiseGain.gain.exponentialRampToValueAtTime(0.001, t + 0.1)
noise.connect(noiseGain).connect(c.destination)
noise.start(t)
}
export function playGameOver() {
const c = getCtx()
const t = c.currentTime
// Descending minor arpeggio
const notes = [440, 349, 294, 220] // A4 F4 D4 A3
notes.forEach((freq, i) => {
const osc = c.createOscillator()
const gain = c.createGain()
osc.type = 'triangle'
osc.frequency.setValueAtTime(freq, t + i * 0.15)
gain.gain.setValueAtTime(0, t)
gain.gain.linearRampToValueAtTime(0.15, t + i * 0.15)
gain.gain.exponentialRampToValueAtTime(0.001, t + i * 0.15 + 0.4)
osc.connect(gain).connect(c.destination)
osc.start(t + i * 0.15)
osc.stop(t + i * 0.15 + 0.4)
})
}