Sound Effects and Music
Last Updated: Oct 19th, 2024
SDL's base audio is complex to say the least. The SDL_mixer extension library makes it much easier to play music and sound effects.//Using SDL, SDL_image, SDL_mixer, and STL string #include <SDL3/SDL.h> #include <SDL3/SDL_main.h> #include <SDL3_image/SDL_image.h> #include <SDL3_mixer/SDL_mixer.h> #include <string>
We're going to be using the SDL_mixer extension library so make sure it's set up in your project.
//Channel constants enum eEffectChannel { eEffectChannelScratch = 0, eEffectChannelHigh = 1, eEffectChannelMedium = 2, eEffectChannelLow = 3, kEffectChannelTotal = 4 };
In order to play a sound effect you need to play it on a channel. We have 4 different sound effects (a scratch and high/medium/low tap effects) and we'll have a channel for each sound effect.
In a real game, you don't need to allocate a channel for each sound effect (in fact you probably shouldn't), we're just doing it to demonstrate how channels work.
In a real game, you don't need to allocate a channel for each sound effect (in fact you probably shouldn't), we're just doing it to demonstrate how channels work.
//The window we'll be rendering to SDL_Window* gWindow{ nullptr }; //The renderer used to draw to the window SDL_Renderer* gRenderer{ nullptr }; //Instruction texture LTexture gPromptTexture; //Playback audio device SDL_AudioDeviceID gAudioDeviceId{ 0 }; //Allocated channel count int gChannelCount = 0; //The music that will be played Mix_Music* gMusic{ nullptr }; //The sound effects that will be used Mix_Chunk* gScratch{ nullptr }; Mix_Chunk* gHigh{ nullptr }; Mix_Chunk* gMedium{ nullptr }; Mix_Chunk* gLow{ nullptr };
In terms of global variables, we're using a SDL_AudioDeviceID to keep track of the audio device we're going to play to,
gChannelCount
to keep track of how many effect channels we have, a
Mix_Music for our music and Mix_Chunks for our sound effects.
bool init() { //Initialization flag bool success{ true }; //Initialize SDL with audio if( !SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) ) { SDL_Log( "SDL could not initialize! SDL error: %s\n", SDL_GetError() ); success = false; }
When using SDL_mixer or SDL audio in general, make sure to pass SDL_INIT_AUDIO to SDL_Init.
//Initialize PNG loading int imgFlags = IMG_INIT_PNG; if( !( IMG_Init( imgFlags ) & imgFlags ) ) { SDL_Log( "SDL_image could not initialize! SDL_image error: %s\n", SDL_GetError() ); success = false; } //Set audio spec SDL_AudioSpec audioSpec; SDL_zero( audioSpec ); audioSpec.format = SDL_AUDIO_F32; audioSpec.channels = 2; audioSpec.freq = 44100; //Open audio device gAudioDeviceId = SDL_OpenAudioDevice( SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audioSpec ); if( gAudioDeviceId == 0 ) { SDL_Log( "Unable to open audio! SDL error: %s\n", SDL_GetError() ); success = false; } else { //Initialize SDL_mixer if( !Mix_OpenAudio( gAudioDeviceId, nullptr ) ) { printf( "SDL_mixer could not initialize! SDL_mixer error: %s\n", SDL_GetError() ); success = false; } }
Much like how we had to initialize SDL_image, we need to initialize SDL_mixer.
First we're specify a SDL_AudioSpec with 32 bit stereo audio at 44.1 Khz. Then we call SDL_OpenAudioDevice to open up our default playback device. Once we have our playback device, we initialize SDL_mixer with it using Mix_OpenAudio.
First we're specify a SDL_AudioSpec with 32 bit stereo audio at 44.1 Khz. Then we call SDL_OpenAudioDevice to open up our default playback device. Once we have our playback device, we initialize SDL_mixer with it using Mix_OpenAudio.
bool loadMedia() { //File loading flag bool success{ true }; //Load scene image if( success &= gPromptTexture.loadFromFile( "15-sound-effects-and-music/prompt.png" ); !success ) { SDL_Log( "Unable to load prompt image!\n" ); } //Load audio if( gMusic = Mix_LoadMUS( "15-sound-effects-and-music/beat.wav" ); gMusic == nullptr ) { SDL_Log( "Unable to load music! SDL_mixer error: %s\n", SDL_GetError() ); success = false; } if( gScratch = Mix_LoadWAV( "15-sound-effects-and-music/scratch.wav" ); gScratch == nullptr ) { SDL_Log( "Unable to load scratch sound! SDL_mixer error: %s\n", SDL_GetError() ); success = false; } if( gHigh = Mix_LoadWAV( "15-sound-effects-and-music/high.wav" ); gHigh == nullptr ) { SDL_Log( "Unable to load high sound! SDL_mixer error: %s\n", SDL_GetError() ); success = false; } if( gMedium = Mix_LoadWAV( "15-sound-effects-and-music/medium.wav" ); gMedium == nullptr ) { SDL_Log( "Unable to load medium sound! SDL_mixer error: %s\n", SDL_GetError() ); success = false; } if( gLow = Mix_LoadWAV( "15-sound-effects-and-music/low.wav" ); gLow == nullptr ) { SDL_Log( "Unable to load low sound! SDL_mixer error: %s\n", SDL_GetError() ); success = false; } //Allocate channels if( success ) { if( gChannelCount = Mix_AllocateChannels( kEffectChannelTotal ); gChannelCount != kEffectChannelTotal ) { SDL_Log( "Unable to allocate channels! SDL_mixer error: %s\n", SDL_GetError() ); success = false; } } return success; }
To load music, we call Mix_LoadMUS and to load our sound effects we call Mix_LoadWAV.
After loading our audio files, we allocate audio channels using Mix_AllocateChannels.
After loading our audio files, we allocate audio channels using Mix_AllocateChannels.
void close() { //Free music Mix_FreeMusic( gMusic ); gMusic = nullptr; //Free sound effects Mix_FreeChunk( gScratch ); gScratch = nullptr; Mix_FreeChunk( gHigh ); gHigh = nullptr; Mix_FreeChunk( gMedium ); gMedium = nullptr; Mix_FreeChunk( gLow ); gLow = nullptr; //Clean up textures gPromptTexture.destroy(); //Close mixer audio Mix_CloseAudio(); //Close audio device SDL_CloseAudioDevice( gAudioDeviceId ); gAudioDeviceId = 0; //Destroy window SDL_DestroyRenderer( gRenderer ); gRenderer = nullptr; SDL_DestroyWindow( gWindow ); gWindow = nullptr; //Quit SDL subsystems Mix_Quit(); IMG_Quit(); SDL_Quit(); }
When we're done with music or a sound effect we call Mix_FreeMusic/Mix_FreeChunk to free them. To close SDL_mixer audio, we call
Mix_CloseAudio and to close the SDL audio device we call SDL_CloseAudioDevice. When we're done with the SDL_mixer library, we call
Mix_Quit
switch( e.key.key ) { //Play high sound effect case SDLK_1: Mix_PlayChannel( eEffectChannelHigh, gHigh, 0 ); break; //Play medium sound effect case SDLK_2: Mix_PlayChannel( eEffectChannelMedium, gMedium, 0 ); break; //Play low sound effect case SDLK_3: Mix_PlayChannel( eEffectChannelLow, gLow, 0 ); break; //Play scratch sound effect case SDLK_4: Mix_PlayChannel( eEffectChannelScratch, gScratch, 0 ); break;
To play a sound effect, call Mix_PlayChannel with the channel you want to play on and the effect you want to play (and if you want to loop it or not and since we don't we set the looping to 0). One thing you should
notice if you press the button rapidly (this is especially noticable on the scratch sound effect) that it will interrupt the effect currently playing to start the effect on the same channel. However if you we to start the scratch effect and quickly start another sound effect it
would not interrupt it because it's on a different channel.
Currently, SDL_mixer creates 8 channels by default but it's a good idea to be explicit in terms of how many channels you need because this default might change. How many channels you need depends on how you want to do your sound design.
Currently, SDL_mixer creates 8 channels by default but it's a good idea to be explicit in terms of how many channels you need because this default might change. How many channels you need depends on how you want to do your sound design.
//If there is no music playing case SDLK_9: if( Mix_PlayingMusic() == 0 ) { //Play the music Mix_PlayMusic( gMusic, -1 ); } //If music is being played else { //If the music is paused if( Mix_PausedMusic() == 1 ) { //Resume the music Mix_ResumeMusic(); } //If the music is playing else { //Pause the music Mix_PauseMusic(); } } break; //Stop the music case SDLK_0: Mix_HaltMusic(); break; }
When we press the 9 key we check if the music is playing with Mix_PlayingMusic. If it isn't, we start the music with Mix_PlayMusic. If the music is playing,
we check if it's paused with Mix_PausedMusic. If it's paused, we resume the music with Mix_ResumeMusic and if it's not we pause it with
Mix_PauseMusic. When we press the 0 key we stop the music with Mix_HaltMusic.