FIX - start 2048 audio reliably

This commit is contained in:
smx.pusha
2026-08-06 08:45:58 +02:00
parent 8c1a79978c
commit 3c92660069
5 changed files with 57 additions and 24 deletions
@@ -9,10 +9,10 @@ type Tone = {
}
const sounds: Record<NumberMergeSound, Tone[]> = {
move: [{ duration: 0.05, frequency: 260, offset: 0, type: 'sine', volume: 0.045 }],
move: [{ duration: 0.065, frequency: 290, offset: 0, type: 'sine', volume: 0.085 }],
merge: [
{ duration: 0.09, frequency: 390, offset: 0, type: 'sine', volume: 0.065 },
{ duration: 0.12, frequency: 520, offset: 0.055, type: 'sine', volume: 0.075 },
{ duration: 0.1, frequency: 390, offset: 0, type: 'sine', volume: 0.1 },
{ duration: 0.14, frequency: 540, offset: 0.055, type: 'sine', volume: 0.12 },
],
win: [
{ duration: 0.16, frequency: 523.25, offset: 0, type: 'sine', volume: 0.08 },
@@ -35,24 +35,43 @@ export function playNumberMergeSound(
): void {
if (!enabled) return
audioContext ??= new AudioContext()
if (audioContext.state === 'suspended') void audioContext.resume()
const now = audioContext.currentTime
for (const tone of sounds[sound]) {
const oscillator = audioContext.createOscillator()
const gain = audioContext.createGain()
const start = now + tone.offset
const end = start + tone.duration
oscillator.type = tone.type
oscillator.frequency.setValueAtTime(tone.frequency, start)
gain.gain.setValueAtTime(0.0001, start)
gain.gain.exponentialRampToValueAtTime(tone.volume, start + 0.012)
gain.gain.exponentialRampToValueAtTime(0.0001, end)
oscillator.connect(gain)
gain.connect(audioContext.destination)
oscillator.start(start)
oscillator.stop(end)
const AudioContextConstructor =
window.AudioContext ??
(window as typeof window & { webkitAudioContext?: typeof AudioContext })
.webkitAudioContext
if (!AudioContextConstructor) {
console.error('[2048 audio] Web Audio is unavailable')
return
}
audioContext ??= new AudioContextConstructor()
const context = audioContext
const scheduleSound = (): void => {
const now = context.currentTime + 0.02
for (const tone of sounds[sound]) {
const oscillator = context.createOscillator()
const gain = context.createGain()
const start = now + tone.offset
const end = start + tone.duration
oscillator.type = tone.type
oscillator.frequency.setValueAtTime(tone.frequency, start)
gain.gain.setValueAtTime(0.0001, start)
gain.gain.linearRampToValueAtTime(tone.volume, start + 0.012)
gain.gain.exponentialRampToValueAtTime(0.0001, end)
oscillator.connect(gain).connect(context.destination)
oscillator.start(start)
oscillator.stop(end + 0.01)
}
}
if (context.state === 'running') {
scheduleSound()
return
}
void context.resume().then(scheduleSound).catch((error: unknown) => {
console.error('[2048 audio] Failed to start sound', error)
})
}
+2
View File
@@ -197,6 +197,8 @@ const defaultLocales: LocaleTree = {
gameOverTitle: 'No more moves',
highest: 'Highest Tile',
howToBody: 'Swipe to combine matching tiles. Each pair becomes one larger tile.',
howToExample: '2 + 2 = 4 · 4 + 4 = 8 · … · 1024 + 1024 = 2048',
howToGoal: 'Goal: create the 2048 tile before the board has no moves left.',
howToTitle: 'How to play',
keepPlaying: 'Keep Playing',
mainMenu: 'Main Menu',
@@ -213,6 +213,8 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown))
<div class="number-merge-how-to">
<strong>{{ phone.t('Apps.numberMerge.howToTitle') }}</strong>
<p>{{ phone.t('Apps.numberMerge.howToBody') }}</p>
<div>{{ phone.t('Apps.numberMerge.howToExample') }}</div>
<small>{{ phone.t('Apps.numberMerge.howToGoal') }}</small>
</div>
</section>
@@ -497,6 +499,14 @@ onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown))
.number-merge-how-to strong { font-size: 10px; text-transform: uppercase; }
.number-merge-how-to p { margin: 3px 0 0; font-size: 9px; line-height: 1.35; }
.number-merge-how-to div {
margin: 6px 0 3px;
color: #a7472d;
font-size: 11px;
font-weight: 900;
letter-spacing: -0.2px;
}
.number-merge-how-to small { display: block; color: #8b6559; font-size: 8px; line-height: 1.25; }
.number-merge-game { padding-top: 5px; }
+3 -1
View File
@@ -89,7 +89,9 @@ Locales["en"] = {
},
eyebrow = "Number puzzle", gameOverBody = "No empty spaces or matching neighbours remain.",
gameOverTitle = "No more moves", highest = "Highest Tile",
howToBody = "Swipe to combine matching tiles. Each pair becomes one larger tile.", howToTitle = "How to play",
howToBody = "Swipe to combine matching tiles. Each pair becomes one larger tile.",
howToExample = "2 + 2 = 4 · 4 + 4 = 8 · … · 1024 + 1024 = 2048",
howToGoal = "Goal: create the 2048 tile before the board has no moves left.", howToTitle = "How to play",
keepPlaying = "Keep Playing", mainMenu = "Main Menu",
menuBody = "Slide matching numbers together and work your way up to 2048.", menuTitle = "Build the 2048 tile",
mute = "Mute game sounds", newGame = "New Game", score = "Score",
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sky Phone</title>
<script type="module" crossorigin src="./assets/sky-index-DUWYfXgl.js"></script>
<script type="module" crossorigin src="./assets/sky-index-CgcZvXN7.js"></script>
<link rel="stylesheet" crossorigin href="./assets/sky-index-yBie1g9x.css">
</head>
<body>