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.

  1. Why Is Sound Important?

  2. Sound Basics

  3. Streaming or Static Loading?

  4. Advanced Playback Control

  5. Cross-Platform Compatibility

  6. Other Sound Libraries (for advanced users)

I’m Impatient!

Users who want to skip to example code should consult the following:

  1. Sound Demo

  2. Sound Speed Demo

  3. Music Control Demo

  4. Platformer Tutorial - Step 9 - Adding Sound

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:

  1. Built-In Resources

  2. pathlib

  3. Streaming or Static Loading?

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 memory

  • Starting 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:

  1. Use the approaching NPC’s pyglet Player to stop its current playback

  2. If 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:

  1. Why Is Sound Important?

  2. Sound Demo

Stopping Sounds#

Arcade’s helper functions are the easiest way to stop playback. To use them:

  1. Do one of the following:

    • Pass the stored pyglet Player to arcade.stop_sound():

      arcade.stop_sound(self.current_playback)
      
    • Pass the stored pyglet Player to the sound’s stop() method:

      self.hurt_sound.stop(self.current_playback)
      
  2. 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

False (Default)

.wav

Whole file

2+ overlapping playbacks, short, repeated, unpredictable

True

.mp3

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:

  1. The Sound will have at most one playback at a time.

  2. The file is long enough to make it worth it.

  3. 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:

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 finished

  • Passing 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:

  1. 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.

  2. 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.

Player Member

Type

Default

Purpose

pause()

method

N/A

Pause playback resumably.

play()

method

N/A

Resume paused playback.

seek()

method

N/A

Skip to the passed float timestamp measured as seconds from the audio’s start.

volume

float property

1.0

The scaling factor to apply to the original audio’s volume. Must be between 0.0 (silent) and 1.0 (full volume).

loop

bool property

False

Whether to restart playback automatically after finishing. [3]

pitch [4]

float property

1.0

How fast to play the sound data; also affects pitch.

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:

  1. The formats which can be loaded

  2. The features supported by playback

  3. The hardware, software, and settings limitations on the first two

  4. The interactions of project requirements with all of the above

The Most Reliable Formats & Features#

For most users, the best approach to formats is:

As long as a user has working audio hardware and drivers, the following basic features should work:

  1. Loading Sounds sound effects from Wave files

  2. Playing Sounds and Stopping Sounds

  3. Adjusting playback volume and speed of playback

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:

  1. pyglet has built-in in support for this format

  2. Some platforms can only play 16-bit audio

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?#

  1. Nearly every system which can run arcade has a supported MP3 decoder.

  2. 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

Audacity

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.

Loading In-Depth#

There are 3 ways arcade can read audio data through pyglet:

  1. The built-in pyglet .wav loading features

  2. Platform-specific components or nearly-universal libraries

  3. 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:

  1. Smaller download & install sizes due to having fewer dependencies

  2. Avoiding binary dependency issues common with PyInstaller and Nuitka

  3. Faster install and loading, especially when using MP3s on slow drives

These benefits become even more important during game jams.

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:

  1. "openal"

  2. "xaudio2"

  3. "directsound"

  4. "pulse"

  5. "silent"

You can override through the ARCADE_SOUND_BACKENDS environment variable. The following rules apply to its value:

  1. It must be a comma-separated string

  2. Each name must be an audio back-ends supported by pyglet

  3. 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:

  1. Why 16-bit PCM Wave for Effects?

  2. Backends Determine Playback Features

  3. The following sections of pyglet’s media guide:

    1. Controlling playback

    2. Positional audio

  4. 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.