This is the final part of our Pac-Man series. In Part 5 we added level progression. Now we bring the game to life with sound effects. We will use the Web Audio API to generate sounds programmatically — no audio files needed.
The Web Audio API needs an AudioContext. We create it on the first user interaction (click or keypress) to comply with browser autoplay policies.
let audioCtx = null;
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext
|| window.webkitAudioContext)();
}
}
// Call initAudio() on first keydown or click
document.addEventListener('keydown', initAudio,
{ once: true });
document.addEventListener('click', initAudio,
{ once: true });
The iconic Pac-Man chomp is a short oscillating tone. We alternate between two pitches to create the "waka-waka" effect.
let wakaToggle = false;
function playWaka() {
if (!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
// Alternate between two frequencies
osc.frequency.value = wakaToggle ? 260 : 330;
osc.type = 'square';
wakaToggle = !wakaToggle;
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(
0.001, audioCtx.currentTime + 0.08);
osc.start(audioCtx.currentTime);
osc.stop(audioCtx.currentTime + 0.08);
}
// Call playWaka() in movePacman() when eating a dot
When Pac-Man eats a frightened ghost, play a rising sweep tone. This feels rewarding and distinct from the chomp.
function playGhostEaten() {
if (!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200,
audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(
800, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(
0.001, audioCtx.currentTime + 0.3);
osc.start(audioCtx.currentTime);
osc.stop(audioCtx.currentTime + 0.3);
}
During frightened mode, play a looping low-frequency siren. This creates tension and tells the player power mode is active.
let sirenOsc = null;
let sirenGain = null;
function startSiren() {
if (!audioCtx || sirenOsc) return;
sirenOsc = audioCtx.createOscillator();
sirenGain = audioCtx.createGain();
sirenOsc.connect(sirenGain);
sirenGain.connect(audioCtx.destination);
sirenOsc.type = 'sine';
sirenOsc.frequency.value = 120;
sirenGain.gain.value = 0.08;
// Wobble the frequency for siren effect
const lfo = audioCtx.createOscillator();
const lfoGain = audioCtx.createGain();
lfo.frequency.value = 4; // 4 Hz wobble
lfoGain.gain.value = 30; // pitch range
lfo.connect(lfoGain);
lfoGain.connect(sirenOsc.frequency);
lfo.start();
sirenOsc.start();
}
function stopSiren() {
if (sirenOsc) {
sirenOsc.stop();
sirenOsc = null;
sirenGain = null;
}
}
// startSiren() when power mode activates
// stopSiren() when power mode ends
When Pac-Man gets caught, play a descending tone that feels like deflation — a classic game over sound.
function playDeath() {
if (!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(600,
audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(
80, audioCtx.currentTime + 0.8);
gain.gain.setValueAtTime(0.25,
audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(
0.001, audioCtx.currentTime + 0.8);
osc.start(audioCtx.currentTime);
osc.stop(audioCtx.currentTime + 0.8);
}
Play a short ascending melody when a new level starts. Three quick notes that build excitement.
function playLevelStart() {
if (!audioCtx) return;
const notes = [330, 440, 550];
const dur = 0.12;
notes.forEach((freq, i) => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.type = 'square';
osc.frequency.value = freq;
const t = audioCtx.currentTime + i * dur;
gain.gain.setValueAtTime(0.15, t);
gain.gain.exponentialRampToValueAtTime(
0.001, t + dur);
osc.start(t);
osc.stop(t + dur);
});
}
Add sound calls to the appropriate places in your game code.
// In movePacman():
if (tile === 2) {
score += SCORE_DOT;
dotsLeft--;
map[ny][nx] = 0;
playWaka(); // chomp sound
}
if (tile === 3) {
score += SCORE_PELLET;
dotsLeft--;
map[ny][nx] = 0;
activatePowerMode();
startSiren(); // power mode siren
}
// In checkGhostCollision():
if (frightened) {
// ... eat ghost logic
playGhostEaten(); // rising sweep
} else {
playDeath(); // descending tone
onPacmanCaught();
}
// In updateFrightened():
if (elapsed > FRIGHTENED_DURATION) {
frightened = false;
stopSiren(); // stop power siren
}
// In showLevelTransition():
playLevelStart(); // ascending jingle
Congratulations — you have built a complete Pac-Man game from scratch! Over 6 parts we covered:
From here, you can keep adding features: mobile swipe controls, a start screen, animations, or even multiplayer. The foundation is solid — have fun building on it.