Sound#
This page will help you get started by covering the essentials of sound.
In addition each section’s concepts, there may also be links to example code and documentation.
Other Sound Libraries (for advanced users)
I’m Impatient!
Users who want to skip to example code should consult the following:
Why Is Sound Important?#
Sound helps players make sense of what they see.
For example, have you ever run into one of these common problems?
Danger you never knew was there
A character whose reaction seemed unexpected or out of place
Items or abilities which appeared similar, but were very different
An unclear warning or confirmation dialog
How much progress did it cost you? A few minutes? The whole playthrough? More importantly, how did you feel? You probably didn’t want to keep playing.
You can use sound to prevent moments like these. In each example above, the right audio can provide the information players need for the game to feel fair.
Sound Basics#
Loading Sounds#
Before you can play a sound, you need to load its data into memory.
Arcade provides two ways to do this. Both accept the same arguments and
return an arcade.Sound
instance.
The easiest way is to use arcade.load_sound()
:
import arcade
# You can pass strings containing a built-in resource handle,
hurt_sound = arcade.load_sound(":resources:sounds/hurt1.wav")
# a pathlib.Path,
pathlib_sound = arcade.load_sound(Path("imaginary\\windows\\path\\file.wav"))
# or an ordinary string describing a path.
string_path_sound = arcade.load_sound("imaginary/mac/style/path.wav")
If you prefer a more object-oriented style, you can create
Sound
instances directly:
from arcade import Sound # You can also use arcade.Sound directly
# Although Sound accepts the same arguments as load_sound,
# only the built-in resource handle is shown here.
hurt_sound = Sound(":resources:sounds/hurt1.wav")
See the following to learn more:
Playing Sounds#
There are two easy ways to play a Sound
object.
One is to call Sound.play
directly:
self.hurt_player = hurt_sound.play()
The other is to pass a Sound
instance as the first
argument of arcade.play_sound()
:
# Important: this *must* be a Sound instance, not a path or string!
self.hurt_player = arcade.play_sound(hurt_sound)
Both return a pyglet.media.player.Player
. You should store
it somewhere if you want to be able to stop or alter a specific playback of
a Sound
’s data.
arcade.Sound
vs pyglet’s Player
#
This is a very important distinction:
An
arcade.Sound
is a source of audio data in memoryStarting a playback of audio data returns a new pyglet
Player
which controls that specific playback
Imagine you have two non-player characters (NPCs) in a game which
both play the same selection of Sound
data. Since
they are separate characters in the world, their playbacks of the data
must be independent. To do this, each NPC will keep the pyglet
Player
returned when they start
playing a sound.
For example, an NPC may get close enough to the user’s character to talk, attack, or perform some other action which requires playing a different sound. You would handle this as follows:
Use the approaching NPC’s pyglet
Player
to stop its current playbackIf the NPC starts playing a different sound, store the returned pyglet
Player
This is especially important when a dangerous NPC or other hazard can be invisible. Making invisible hazards play sounds is one of the easiest and most popular ways of making their gameplay feel balanced, fair, and fun.
See the following to learn more:
Stopping Sounds#
Arcade’s helper functions are the easiest way to stop playback. To use them:
Do one of the following:
Pass the stored pyglet
Player
toarcade.stop_sound()
:arcade.stop_sound(self.current_playback)
Pass the stored pyglet
Player
to the sound’sstop()
method:self.hurt_sound.stop(self.current_playback)
Clear any references to the player to allow its memory to be freed:
# For each object, Python tracks how many other objects use it. If # nothing else uses an object, it will be marked as garbage which # Python can delete automatically to free memory. self.current_playback = None
See the following to learn more:
Streaming or Static Loading?#
Streaming |
Best [1] Format |
Decompressed |
Best Uses |
---|---|---|---|
|
|
Whole file |
2+ overlapping playbacks, short, repeated, unpredictable |
|
|
Predicted data |
1 copy & file at a time, long, uninterrupted |
By default, arcade decompresses the entirety of each sound into memory.
This is the best option for most game sound effects. It’s called “static” [2] audio because the data never changes.
The alternative is streaming. Enable it by passing True
through the
streaming
keyword argument when you load a sound:
# Both loading approaches accept the streaming keyword.
classical_music_track = arcade.load_sound(":resources:music/1918.mp3", streaming=True)
funky_music_track = arcade.Sound(":resources:music/funkyrobot.mp3", streaming=True)
For an interactive example, see the Music Control Demo.
The following subheadings will explain each option in detail.
Static Sounds are for Speed#
Static sounds can help your game run smoothly by preloading data before gameplay.
This is because disk access is one of the slowest things a computer can do. Waiting for sounds to load during gameplay can make the your game run slowly or stutter. The best way to prevent this is to load your sound data ahead of time. Popular approaches for this include:
Loading screens
Small inter-level “rooms”
Multi-threading (best used by experienced programmers)
Unless music is a central part of your gameplay, you should avoid storing fully decompressed albums of music in RAM. Each decompressed minute of CD quality audio uses slightly over 10 MB of RAM. This adds up quickly, and can slow down or freeze a computer if it fills RAM completely.
For music and long background audio, you should strongly consider streaming from compressed files instead.
When to Use Static Sounds#
If an audio file meets one or more of the following conditions, you may want to load it as static audio:
You need to start playback quickly in response to gameplay.
Two or more “copies” of the sound can be playing at the same time.
You will unpredictably skip to different times in the file.
You will unpredictably restart playback.
You need to automatically loop playback.
The file is a short clip.
Streaming Saves Memory#
Streaming audio from files is very similar to streaming video online.
Both save memory by keeping only part of a file into memory at any given time. Even on the slowest recent hardware, this usually works if:
You only stream one media source at a time.
You don’t need to synchronize it closely with anything else.
When to Stream#
The best way to use streaming is to only use it when you need it.
Advanced users may be able to handle streaming multiple tracks at a time. However, issues with synchronization & interruptions will grow with the quantity and quality of the audio tracks involved.
If you’re unsure, avoid streaming unless you can say yes to all of the following:
The
Sound
will have at most one playback at a time.The file is long enough to make it worth it.
Seeking (skipping to different parts) will be infrequent.
Ideally, you will never seek or restart playback suddenly.
If you do seek, the jumps will ideally be close enough to land in the same or next chunk.
See the following to learn more:
The
pyglet.media.StreamingSource
class used to implement streaming
Streaming Can Cause Freezes#
Failing to meet the requirements above can cause buffering issues.
Good compression on files can help, but it can’t fully overcome it. Each skip outside the currently loaded data requires reading and decompressing a replacement.
In the worst-case scenario, frequent skipping will mean constantly buffering instead of playing. Although video streaming sites can downgrade quality, your game will be at risk of stuttering or freezing.
The best way to handle this is to only use streaming when necessary.
Advanced Playback Control#
Arcade’s functions for Stopping Sounds are convenience
wrappers around the passed pyglet Player
.
You can alter a playback of Sound
data with more precision
by:
Using the properties and methods of its
Player
any time before playback has finishedPassing keyword arguments with the same (or similar) names as the Player’s properties when playing the sound.
Stopping via the Player Object#
The simplest form of advanced control is pausing and resuming playback.
Pausing#
There is no stop method. Instead, call the Player.pause()
method:
# Assume this is inside an Enemy class subclassing arcade.Sprite
self.current_player.pause()
Stopping Permanently#
After you’ve paused a player, you can stop playback permanently:
Call the player’s
delete()
method:# Permanently deletes the operating system half of this playback. self.current_player.delete()
This specific playback is now permanently over, but you can start new ones.
Make sure all references to the player are replaced with
None
:# Python will delete the pyglet Player once there are 0 references to it self.current_player = None
For a more in-depth explanation of references and auto-deletion, skim the start of Python’s page on garbage collection. Reading the Abstract section of this page should be enough to get started.
Changing Aspects of Playback#
There are more ways to alter playback than stopping. Some are more qualitative. Many of them can be applied to both new and ongoing sound data playbacks, but in different ways.
Change Ongoing Playbacks via Player Objects#
Player.pause()
is one of
many method and property members which change aspects of an ongoing
playback. It’s impossible to cover them all here, especially given the
complexity of positional audio.
Instead, the table below summarizes a few of the most useful members in the context of arcade. Superscripts link info about potential issues, such as name differences between properties and equivalent keyword arguments to arcade functions.
|
Type |
Default |
Purpose |
---|---|---|---|
method |
N/A |
Pause playback resumably. |
|
method |
N/A |
Resume paused playback. |
|
method |
N/A |
Skip to the passed |
|
|
|
The scaling factor to apply to the original audio’s volume. Must
be between |
|
|
|
Whether to restart playback automatically after finishing. [3] |
|
|
|
How fast to play the sound data; also affects pitch. |
Looping is unavailable when streaming=True
; see pyglet’s guide to
controlling playback.
Arcade’s equivalent keyword for Playing Sounds is speed
Configure New Playbacks via Keyword Arguments#
Arcade’s helper functions for playing sound also accept keyword
arguments for configuring playback. As mentioned above, the names of
these keywords are similar or identical to those of properties on
Player
. See the following to learn
more:
Cross-Platform Compatibility#
The sections below cover the easiest approach to compatibility.
You can try other options if you need to. Be aware that doing so requires grappling with the many factors affecting audio compatibility:
The formats which can be loaded
The features supported by playback
The hardware, software, and settings limitations on the first two
The interactions of project requirements with all of the above
The Most Reliable Formats & Features#
For most users, the best approach to formats is:
Use 16-bit PCM Wave (
.wav
) files for sound effectsUse MP3 files for long background audio like music
As long as a user has working audio hardware and drivers, the following basic features should work:
Loading Sounds sound effects from Wave files
Advanced functionality or subsets of it may not, especially positional audio. To learn more, see the rest of this page and pyglet’s guide to supported media types.
Why 16-bit PCM Wave for Effects?#
Storing sound effects as 16-bit PCM .wav
ensures all users can load them:
The files must also be mono rather than stereo if you want to use positional audio.
Accepting these limitations is usually worth the compatibility benefits, especially as a beginner.
Why MP3 For Music and Ambiance?#
Nearly every system which can run arcade has a supported MP3 decoder.
MP3 files are much smaller than Wave equivalents per minute of audio, which has multiple benefits.
See the following to learn more:
Converting Audio Formats#
Don’t worry if you have a great sound in a different format.
There are multiple free, reliable, open-source tools you can use to convert existing audio. Two of the most famous are summarized below.
Name & Link for Tool |
Difficulty |
Summary |
---|---|---|
Beginner [5] |
A free GUI application for editing sound |
|
FFmpeg’s command line tool |
Advanced |
Powerful media conversion tool included with the library |
Most versions of these tools should handle the following common tasks:
Converting audio files from one encoding format to another
Converting from stereo to mono for use with positional audio.
To integrate FFmpeg with Arcade as a decoder, you must use FFmpeg version 4.X, 5.X, or 6.X. See Loading In-Depth to learn more.
Linux users may need to install the LAME MP3 encoder separately to export MP3 files.
Loading In-Depth#
There are 3 ways arcade can read audio data through pyglet:
The built-in pyglet
.wav
loading featuresPlatform-specific components or nearly-universal libraries
Supported cross-platform media libraries, such as PyOgg or FFmpeg
To load through FFmpeg, you must install FFmpeg 4.X, 5.X, or 6.X. This is a requirement imposed by pyglet. See pyglet’s notes on installing FFmpeg to learn more.
Everyday Usage#
In practice, Wave is universally supported and MP3 nearly so. [6]
Limiting yourself to these formats is usually worth the increased compatibility doing so provides. Benefits include:
Smaller download & install sizes due to having fewer dependencies
Avoiding binary dependency issues common with PyInstaller and Nuitka
Faster install and loading, especially when using MP3s on slow drives
These benefits become even more important during game jams.
The only time MP3 will be absent is on unusual Linux configurations. See pyglet’s guide to supported media types to learn more.
Backends Determine Playback Features#
As with formats, you can maximize compatibility by only using the lowest common denominators among features. The most restrictive backends are:
Mac’s only backend, an OpenAL version limited to 16-bit audio
PulseAudio on Linux, which lacks support for common features such as positional audio.
On Linux, the best way to deal with the PulseAudio backend’s limitations is to install OpenAL. It will often already be installed as a dependency of other packages.
Other differences between backends are less drastic. Usually, they will be things like the specific positional features supported and the maximum number of simultaneous sounds.
See the following to learn more:
Choosing the Audio Backend#
By default, arcade will try pyglet audio back-ends in the following order until it finds one which loads:
"openal"
"xaudio2"
"directsound"
"pulse"
"silent"
You can override through the ARCADE_SOUND_BACKENDS
environment
variable. The following rules apply to its value:
It must be a comma-separated string
Each name must be an audio back-ends supported by pyglet
Spaces do not matter and will be ignored
For example, you could need to test OpenAL on a specific system. This example first tries OpenAL, then gives up instead using fallbacks.
ARCADE_SOUND_BACKENDS="openal,silent" python mygame.py
Please see the following to learn more:
Other Sound Libraries#
Advanced users may have reasons to use other libraries to handle sound.
Using Pyglet#
The most obvious external library for audio handling is pyglet:
It’s guaranteed to work wherever arcade’s sound support does.
It offers far better control over media than arcade
You may have already used parts of it directly for Advanced Playback Control
Note that arcade.Sound
’s source
attribute holds a
pyglet.media.Source
. This means you can start off by cleanly
using arcade’s resource and sound loading with pyglet features as needed.
Notes on Positional Audio#
Positional audio is a set of features which automatically adjust sound volumes across the channels for physical speakers based on in-game distances.
Although pyglet exposes its support for this through its
Player
, arcade does not currently offer
integrations. You will have to do the setup work yourself.
If you already have some experience with Python, the following sequence of links should serve as a primer for trying positional audio:
The following sections of pyglet’s media guide:
pyglet.media.player.Player
’s full documentation
External Libraries#
Some users have reported success with using PyGame CE or SDL2 to handle sound. Both these and other libraries may work for you as well. You will need to experiment since this isn’t officially supported.