Sound in practice
Load and play sound effects with pygame.mixer.Sound, control per-sound volume, and stream background music with pygame.mixer.music.
- Load a Sound object and call play() at the right trigger point
- Adjust per-sound volume with set_volume()
- Load, loop, and volume-control background music with pygame.mixer.music
This lesson shows the complete audio API as working code. Because real .wav
files are not available in every project, the code uses pygame.sndarray to
generate a short sine-wave tone programmatically — this lets you hear audio
working without needing external assets.
Pyodide (the in-browser Python runner) does not support pygame's display or
mixer system. Read through this code carefully in the browser, then run it
locally: pip install pygame numpy followed by python sound_demo.py.
The code
import pygame
import sys
import numpy as np
pygame.init()
pygame.mixer.init(frequency=44100, size=-16, channels=1, buffer=512)
SCREEN_W, SCREEN_H = 640, 480
screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
pygame.display.set_caption("Sound demo")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 30)
# ── Generate a simple tone as a Sound object ──────────────────────────────────
# In a real project, replace this with:
# jump_sfx = pygame.mixer.Sound("assets/jump.wav")
def make_tone(frequency=440, duration=0.15, volume=0.4, sample_rate=44100):
"""Return a pygame.mixer.Sound containing a short sine-wave tone."""
n_samples = int(sample_rate * duration)
t = np.linspace(0, duration, n_samples, endpoint=False)
wave = (np.sin(2 * np.pi * frequency * t) * 32767 * volume).astype(np.int16)
sound = pygame.sndarray.make_sound(wave)
return sound
jump_sfx = make_tone(frequency=520, duration=0.12) # higher pitch = jump
coin_sfx = make_tone(frequency=880, duration=0.08) # bright ding = coin
hit_sfx = make_tone(frequency=220, duration=0.18) # low thud = hit
# Volume is 0.0 to 1.0. Set it once after loading.
jump_sfx.set_volume(0.6)
coin_sfx.set_volume(0.8)
hit_sfx.set_volume(0.5)
# ── Background music ──────────────────────────────────────────────────────────
# In a real project:
# pygame.mixer.music.load("assets/music.ogg")
# pygame.mixer.music.set_volume(0.4)
# pygame.mixer.music.play(-1) # -1 = loop forever
#
# Skipped here because we have no music file, but the API is three lines.
# ── Simple demo: press keys to trigger SFX ───────────────────────────────────
log = [] # last few triggered events, shown on screen
running = True
while running:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
jump_sfx.play()
log.append("Jump SFX played")
if event.key == pygame.K_c:
coin_sfx.play()
log.append("Coin SFX played")
if event.key == pygame.K_h:
hit_sfx.play()
log.append("Hit SFX played")
# Keep last 5 messages
log = log[-5:]
screen.fill((20, 20, 35))
screen.blit(font.render(
"SPACE = jump C = coin H = hit", True, (200, 200, 200)), (10, 10))
for i, msg in enumerate(log):
screen.blit(font.render(msg, True, (255, 255, 180)), (10, 50 + i * 26))
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()How each part works
pygame.mixer.init(frequency, size, channels, buffer) configures the
mixer before you load any sounds. frequency=44100 is CD quality; size=-16
is 16-bit signed samples; channels=1 is mono (use 2 for stereo);
buffer=512 reduces latency. Call this once, before any Sound or music
calls.
pygame.mixer.Sound(path) loads the file into memory. The returned object
is reusable — calling .play() multiple times creates independent playback
instances, so overlapping effects work without extra code.
.set_volume(n) takes a float between 0.0 and 1.0. Set it after loading;
it persists until changed. You can also call .set_volume() on each individual
.play() return value to control that specific instance.
pygame.mixer.music.load(path) prepares a file for streaming. Unlike
Sound.load(), only one music file can be loaded at a time. play(-1) loops
indefinitely; play(0) plays once; play(3) loops three additional times after
the first play.
When transitioning between scenes, call pygame.mixer.music.stop() before
loading the next track. Failing to stop the previous music before loading a
new file can cause a brief audio glitch on some platforms.
Where to go next
Next: saving game state — deciding what is worth persisting between sessions and preparing your data for serialisation.
Sound effects
Use pygame.mixer to load short clips and trigger them at the right moments — and understand channel limits before they bite you.
Saving game state
Decide what state is worth persisting, understand the boundary between session state and persistent state, and make your data JSON-safe before writing it to disk.