Tutorial de SDL – Parte 21 – Efeitos sonoros e música

Continuando nossa série de artigos traduzido do site lazyfoo, agora veremos como adicionar sons e música ao jogo que estamos criando.

Até agora estivemos lidando apenas com vídeo e entrada de dados. Muitos jogos também necessitam de algum tipo de som e aqui estaremos usando SDL_mixer para tocar o áudio para nós.
//Using SDL, SDL_image, SDL_mixer, standard IO, and strings
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <stdio.h>
#include <string>
SDL_Mixer é uma biblioteca que usamos para facilitar a execução de áudio (pois pode ficar complicado). Precisamos configura-la da mesma forma que fizemos com  SDL_image. Como antes, é somente uma questão de ter os arquivo de cabeçalho, de biblioteca e binários nos locais apropriados e seu compilador configurado para usa-los.
//The music that will be played
Mix_Music *gMusic = NULL;
//The sound effects that will be used
Mix_Chunk *gScratch = NULL;
Mix_Chunk *gHigh = NULL;
Mix_Chunk *gMedium = NULL;
Mix_Chunk *gLow = NULL;
O tipo de dados do SDL_mixer para música é Mix_Music e para um som curto é Mix_Chunk. Aqui declaramos ponteiros para a música e efeitos sonoros que estaremos usando.
//Initialize SDL
if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0 )
{
   printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError() );
   success = false;
}
Como usaremos música e efeitos sonoros, precisamos inicializar áudio junto com o vídeo no nosso exemplo.
//Initialize PNG loading
int imgFlags = IMG_INIT_PNG;
if( !( IMG_Init( imgFlags ) & imgFlags ) )
{
   printf( "SDL_image could not initialize! SDL_image Error: %s\n", IMG_GetError() );
   success = false;
}
//Initialize SDL_mixer
if( Mix_OpenAudio( 44100, MIX_DEFAULT_FORMAT, 2, 2048 ) < 0 )
{
   printf( "SDL_mixer could not initialize! SDL_mixer Error: %s\n", Mix_GetError() );
   success = false;
}

Para inicializar o SDL_mixer precisamos chamar Mix_OpenAudio. O primeiro argumento é a frequência, sendo 44100 uma frequência padrão que funciona na maioria dos sistemas. O segundo argumento determina o formato da amostra, que novamente estaremos usando o padrão. O tericeiro argumento é o número de canais de hardware, que usaremos 2 para áudio stereo. O último argumento é o tamanho da amostra, que determina o tamanho de cada pedaço (chunk) que usaremos quando tocamos um som. 2048 bytes (ou 2 kilobytes) funciona bem na maioria dos casos, mas você pode ter que fazer alguns experimentos com esse valor para minimizar atrasos durante a execução dos sons.

Se ocorrer algum erro com o SDL_mixer, eles são reportados com Mix_GetError.

bool loadMedia()
{
   //Loading success flag
   bool success = true;
   //Load prompt texture
   if( !gPromptTexture.loadFromFile( "21_sound_effects_and_music/prompt.png" ) )
{
   printf( "Failed to load prompt texture!\n" );
   success = false;
}
//Load music
gMusic = Mix_LoadMUS( "21_sound_effects_and_music/beat.wav" );
if( gMusic == NULL )
{
   printf( "Failed to load beat music! SDL_mixer Error: %s\n", Mix_GetError() );
   success = false;
}
//Load sound effects
gScratch = Mix_LoadWAV( "21_sound_effects_and_music/scratch.wav" );
if( gScratch == NULL )
{
   printf( "Failed to load scratch sound effect! SDL_mixer Error: %s\n", Mix_GetError() );
   success = false;
}
gHigh = Mix_LoadWAV( "21_sound_effects_and_music/high.wav" );
if( gHigh == NULL )
{
   printf( "Failed to load high sound effect! SDL_mixer Error: %s\n", Mix_GetError() );
   success = false;
}
gMedium = Mix_LoadWAV( "21_sound_effects_and_music/medium.wav" );
if( gMedium == NULL )
{
   printf( "Failed to load medium sound effect! SDL_mixer Error: %s\n", Mix_GetError() );
   success = false;
}
gLow = Mix_LoadWAV( "21_sound_effects_and_music/low.wav" );
if( gLow == NULL )
{
   printf( "Failed to load low sound effect! SDL_mixer Error: %s\n", Mix_GetError() );
   success = false;
}
return success;
}

Aqui carregamos nossa textura e o som.

Para carregar uma música, chamamos Mix_LoadMUS e para carregar um efeito sonoro chamamos Mix_LoadWAV.

void close()
{
   //Free loaded images
   gPromptTexture.free();
   //Free the sound effects
   Mix_FreeChunk( gScratch );
   Mix_FreeChunk( gHigh );
   Mix_FreeChunk( gMedium );
   Mix_FreeChunk( gLow );
   gScratch = NULL;
   gHigh = NULL;
   gMedium = NULL;
   gLow = NULL;
   //Free the music
   Mix_FreeMusic( gMusic );
   gMusic = NULL;
   //Destroy window
   SDL_DestroyRenderer( gRenderer );
   SDL_DestroyWindow( gWindow );
   gWindow = NULL;
   gRenderer = NULL;
   //Quit SDL subsystems
   Mix_Quit();
   IMG_Quit();
   SDL_Quit();
}
Quando tivermos finalizado com o uso de áudio e quisermos liberar os recursos utilizados, chamamos Mix_FreeMusic para liberar recursos utilizado pelo música e Mix_FreeChunk para liberar recursos utilizados por efeitos sonoros. Chamamos Mix_Quit para encerrar o SDL_mixer.
//Handle key press
else if( e.type == SDL_KEYDOWN )
{
   switch( e.key.keysym.sym )
   {
      //Play high sound effect
      case SDLK_1:
         Mix_PlayChannel( -1, gHigh, 0 );
         break;
      //Play medium sound effect
      case SDLK_2:
         Mix_PlayChannel( -1, gMedium, 0 );
         break;
      //Play low sound effect
      case SDLK_3:
         Mix_PlayChannel( -1, gLow, 0 );
         break;
      //Play scratch sound effect
      case SDLK_4:
         Mix_PlayChannel( -1, gScratch, 0 );
         break;

No loop de eventos, executamos um efeito sonoro quando as teclas 1, 2, 3 ou 4 são pressionadas. A forma de executar um Mix_Chunk é chamando a função Mix_PlayChannel. O primeiro argumento é o canal que você quer usar para executar o som. Como não nos importamos qual canal é usado, usamos o valor -1 para esse argumento que indica o canal mais próximo disponível. O segundo argumento é o efeito sonoro e o último argumento é o número de vezes que o som será repetido. Como queremos executar o som apenas uma vez quando a tecla é pressionada, temos que repetir 0 vezes.

Um canal nesse caso não é o mesmo que um canal de hardware que pode representar os lados esquerdo e direito em um sistema stereo. Cada efeito sonora que é executado possui um canal associado a ele. Quando você quer pausar ou parar um som que está sendo tocado, pode encerrar seu canal.

case SDLK_9:
//If there is no music playing
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;
case SDLK_0:
   //Stop the music
   Mix_HaltMusic();
   break;
}
}

Nesse exemplo, queremos tocar/pausar a música quando a tecla 9 é pressionada e para-la quando a tecla 0 é pressionada.

Quando a tecla 9 é pressionada, verificamos primeiro se a música não está sendo tocada com Mix_PlayingMusic. Se não estiver, iniciamos a música com Mix_PlayMusic. O primeiro argumento é a música que queremos executar e o último argumento é o número de vezes que ela será repetida. o valor -1 é um valor especial que informa que queremos repeti-la até que ela seja parada.

Se houver uma música sendo executada, verificamos se a música está pausa usando Mix_PausedMusic. Se estiver pausada, encerramos com Mix_ResumeMusic. Se não estiver pausada, nós pausamos a música com Mix_PauseMusic.

Quando a tecla 0 é pressionada, paramos a música, caso estiver sendo executada, com Mix_HaltMusic.

Baixe os arquivo do código fonte para esse artigo aqui.