Curso completo de DirectX 9 com C\C++
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
Fase 08-1
08.1 Efeitos sonoros
1.1 Visão geral
Neste capítulo vamos aprender como adicionar efeitos sonoros na
aplicação. A infraestrutura utilizada é a do DirectSound que é a parte
do directx responsável pela manipulação de efeitos sonoros contidos
em arquivos do formato wave (wav).
A intenção desse tópico é proporcionar um caminho fácil e rápido de
carregar um efeito sonoro, tocá-lo ou pará-lo de acordo com a
necessidade da aplicação. Para a realização desse propósito vamos
utilizar a classe CSoundPlayer elaborada pelo professor Keith
Ditchburn disponibilizada no site www.toymaker.info, que é um site
excelente sobre o directx 9.
Foi completamente evitado explicações diretas sobre o DirectSound
visto que a classe citada permite o uso de efeitos sonoros de maneira
simples, fácil e rápida.
Existe um pequeno conjunto de classes contidas no arquivo dsutil.cpp
que simplificam o uso básico do directsound. Algumas destas classes
são utilizadas pela classe CSoundPlayer na realização do seu trabalho:
- CSoundManager - faz gerenciamento do dispositivo de som;
- CSound - manipulação dos efeitos sonoros;
- CWaveFile - faz leitura e gravação do arquivo wav.
Informamos que estas classes não serão cobertas. O foco será dado no
aproveitamento das funcionalidades da classe CSoundPlayer.
Informamos que os arquivos da composição desse projeto não são fiéis
em relação às fontes originais pois sofreram simplificações,
traduções e adaptações para se enquadrarem na perspectiva desse curso.
Portanto, utilize-os como base para sonorizar suas aplicações visto
que os originais não vão oferecer o mesmo caminho fácil de uso.
1.2 Estrutura principal da aplicação
Arquivo: entrada.cpp
Aspectos globais
Declaração global do objeto da classe CSoundPlayer (g_Tocador)
e do handle da janela (hJanela)
iniciar()
Configura a janela de console
menu()
Mostra as teclas para controle de execução do efeito sonoro
main()
Chama iniciar() e menu() para inicializar a aplicação
Pega o handle da janela de console e inicializa o directsound
Carrega um efeito sonoro e toca-o inicialmente.
Através de algumas teclas o usuário consegue selecionar o
efeito sonoro, tocar e pará-lo.
Arquivos: motor.h - dsutil.h\dsutil.cpp
As classes CSound, CWaveFile, CStreamingFile são os alicerces da
classe CSoundManager.

A classe CSoundManager é o alicerce da classe final CSoundPlayer
que faz a interface com a aplicação para tocar efeitos sonoros.
* a repetição de certas classes nos diagramas tem a função de manter
a boa estética do diagrama, entretanto, mostra a relação de afinidade
e proximidade das classes.
Arquivo: SoundPlayer.cpp - classe CSoundPlayer
CSoundManager *m_soundManager;
Esta é uma instância de um objeto da classe
CSoundManager que vai
ser responsável por criar um objeto da interface
IDirectSound8 que
representa o dispositivo de som utilizado para sonorizar a aplicação.
std::vector <CSound*> m_soundVector;
Este objeto vai representar a coleção de efeitos sonoros carregados
pela aplicação.
bool AddWav(char *filename, int *id);
Este método adiciona um arquivo sonoro na coleção e produz um id
numérico para identificar esse som nos futuros processos que serão
aplicados sobre ele.
bool PlaySound(int id, bool loop);
Esse método toca o som apontado pelo id e repete-o continuamente se o
flag de repetição (
loop) estiver configurado como TRUE.
void StopSound(int id);
Este método pára o som identificado pelo id.
HRESULT Initialise(HWND hWnd);
Este método inicializa o gerenciador de som da classe
CSoundManager e
outros objetos necessários para a utilização de efeitos sonoros.
CSoundPlayer();
virtual ~CSoundPlayer();
Aqui estão o construtor e o destrutor da classe.
2.1.1 Aspectos globais - Arquivo: motor.h
//*******************************************************************
// Projeto: prj_consoleSom - Arquivo: motor.h
// Esta aplicação exemplifica como tocar um efeito sonoro
// contido em um arquivo do formato wav
// By www.toymaker.info \\ www.gameprog.com.br
//*******************************************************************
#ifndef motor_h
#define motor_h
// Exclui conteúdo raramente usado para agilizar compilação
#define WIN32_LEAN_AND_MEAN
// Funcionalidades do Windows
#include <windows.h>
// Funcionalidades da linguagem C
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <assert.h>
// Acesso multimídia
#include <mmsystem.h>
#include <dxerr9.h>
#include <dsound.h>
//-----------------------------------------------------------------------------
// Macros de utilidades
//-----------------------------------------------------------------------------
// Deleta memória alocada com new
#define SAFE_DELETE(p) { if(p) { delete (p); (p) = NULL; } }
// Deleta memória alocada com new em arrays
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p) = NULL; } }
// Libera interfaces do directx
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p) = NULL; } }
#endif
motor.h
Este arquivo foi montado para dar suporte ao arquivo dsutil.cpp que
faz uso das macros e dos arquivos incluídos. Então para sonorizar
suas futuras aplicações é essencial transportar para seu projeto o
conteúdo desse arquivo.
#define WIN32_LEAN_AND_MEAN
O uso dessa macro é para simplificar a compilação do projeto. O uso
dessa macro desabilita a inclusão de arquivos de cabeçalhos com
funcionalidades raramente usadas; por exemplo, exclusão das funções
de criptografia que não vem ao caso desse projeto.
Essa macro deve ser utilizada antes da inclusão do arquivo windows.h.
#include <mmsystem.h>
O DirectSound utiliza o temporizador oferecido neste arquivo de
cabeçalho. O arquivo lib referente a ele é winmm.lib.
#include <dxerr9.h>
Este arquivo contém definições de erros e exceções do directx 9.
#include <dsound.h>
Este aqui é o arquivo de cabeçalho do DirectSound. O arquivo lib
referente é dsound.lib.
#define SAFE_DELETE(p) { if(p) { delete (p); (p) = NULL; } }
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p) = NULL; } }
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p) = NULL; } }
Esta coleção de macros encurtam a digitação do código de liberação
dos objetos das interfaces e de memória alocada com new.
Por exemplo, SAFE_RELEASE(g_device) é expandido para
if (g_device) {
g_device->Release();
g_device = NULL; }
2.1.2 Aspectos globais - A classe CSoundPlayer
//*******************************************************************
// SoundPlayer.h: Interface para a classe CSoundPlayer.
// Descrição: Usa o DirectSound para tocar sons
// Nota: Faz uso dos arquivos dsutil.cpp e dsutil.h que pertencem
// ao framework do directx. Estes arquivos definem as classes
// CSoundManager e CSound usadas por este programa
// Criado por Keith Ditchburn 30/01/2004
//*******************************************************************
#ifndef soundplayer_h
#define soundplayer_h
#include <vector>
// Classes utilizadas na implementação deste arquivo
class CSoundManager;
class CSound;
class CSoundPlayer
{
private:
// Objeto gerenciador de som
CSoundManager *m_soundManager;
// Coleção de arquivos de som
std::vector <CSound*> m_soundVector;
public:
// Adiciona um arquivo wav ao vetor produzindo um id
bool AddWav(char *filename, int *id);
// Toca o som com repetição continua se loop = TRUE
bool PlaySound(int id, bool loop);
// Pára de tocar o som
void StopSound(int id);
// Inicializa o objeto tocador de som (CSoundManager)
HRESULT Initialise(HWND hWnd);
// Construtor
CSoundPlayer();
// Destrutor
virtual ~CSoundPlayer();
};
#endif
#include <vector>
A classe CSoundPlayer utiliza o recipiente STL vector para manter os
arquivos sonoros wav adicionados pela aplicação.
class CSoundManager;
class CSound;
Estas classes são utilizadas pela classe CSoundPlayer para adicionar
uma sonorização simples à aplicação.
CSoundManager *m_soundManager;
Esta é uma instância de um objeto da classe CSoundManager que vai
ser responsável por criar um objeto da interface IDirectSound8 que
representa o dispositivo de som utilizado para sonorizar a aplicação.
std::vector <CSound*> m_soundVector;
Este objeto vai representar a coleção de efeitos sonoros carregados
pela aplicação.
bool AddWav(char *filename, int *id);
Este método adiciona um arquivo sonoro na coleção e produz um id
numérico para identificar esse som nos futuros processos que serão
aplicados sobre ele.
bool PlaySound(int id, bool loop);
Esse método toca o som apontado pelo id e repete-o continuamente se o
flag de repetição (loop) estiver configurado como TRUE.
void StopSound(int id);
Este método pára o som identificado pelo id.
HRESULT Initialise(HWND hWnd);
Este método inicializa o gerenciador de som da classe CSoundManager e
outros objetos necessários para a utilização de efeitos sonoros.
CSoundPlayer();
virtual ~CSoundPlayer();
Aqui estão o construtor e o destrutor da classe.
2.1.3 Aspectos globais - Arquivo: entrada.cpp
// Projeto: prj_consoleSom - Arquivo: entrada.cpp
// Esta aplicação exemplifica como tocar um efeito sonoro
// contido em um arquivo do formato wav
// By www.toymaker.info \\ www.gameprog.com.br
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <windows.h>
#include <wincon.h>
#include "SoundPlayer.h"
// Inclusão das bibliotecas
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dsound.lib")
// Protótipo das funções
void iniciar(void);
void menu(void);
// Ponteiro para um objeto CSoundPlayer
CSoundPlayer *g_Tocador = NULL;
// Vamos precisar do handle da janela de console
HWND hJanela;
#include <wincon.h>
Vamos precisar da função GetConsoleWindow() desse arquivo para
conectar a janela de console com o dispositivo de som.
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dsound.lib")
Estas bibliotecas permitem o uso das funcionalidades do DirectSound.
void iniciar(void);
A função iniciar() faz uma configuração básica na janela de console.
void menu(void);
A função menu() mostra as teclas de controle dos efeitos sonoros.
CSoundPlayer *g_Tocador = NULL;
Este objeto é a interface pela qual a aplicação adiciona e controla
os efeitos sonoros.
HWND hJanela;
Este handle de janela vai ser usado para coletar o handle da janela
de console necessário para a inicialização do DirectSound.
2.2 Utilização da classe CSoundPlayer na função main()
int main(void)
{
// Identifica qual efeito sonoro tocar
int nSom = 0;
// Controle de pressionamento das teclas
int ntecla = 0;
int nchar = 0;
// Inicializa a janela de console e mostra menu
iniciar();
menu();
// Cria um objeto CSoundPlayer
g_Tocador = new CSoundPlayer;
// Inicializa o gerenciador de som
hJanela = GetConsoleWindow ();
g_Tocador->Initialise(hJanela);
// Adiciona efeitos sonoros na fila
g_Tocador->AddWav("drum1.wav", &nSom);
g_Tocador->AddWav("drum2.wav", &nSom);
// Toca um som inicialmente
g_Tocador->PlaySoundA(0, false);
// Pressione a letra 'Q' para quebrar o laço
while(true)
{
ntecla = _kbhit();
if (ntecla != 0) nchar = _getch();
// Indicação de qual id de som para manipular
if(nchar == '1') nSom = 0;
if(nchar == '2') nSom = 1;
// Mostra o som sendo manipulado na janela
if(nchar == '1') system("title prj_consoleSom: drum1.wav");
if(nchar == '2') system("title prj_consoleSom: drum2.wav");
// Toca o som indicado
if(nchar == 'P') g_Tocador->PlaySoundA(nSom, false);
if(nchar == 'p') g_Tocador->PlaySoundA(nSom, false);
// Pára o som que está sendo tocado
if(nchar == 'S') g_Tocador->StopSound (nSom);
if(nchar == 's') g_Tocador->StopSound (nSom);
// Toca o som continuamente (looping)
if(nchar == 'L') g_Tocador->PlaySoundA (nSom, true);
if(nchar == 'l') g_Tocador->PlaySoundA (nSom, true);
// Encerra a aplicação
if(nchar == 'Q') break;
if(nchar == 'q') break;
// Limpa as variáveis de leitura do teclado
if(nchar != 'Q' ) nchar = 0;
ntecla = 0;
} // endwhile
// Libera o objeto CSoundPlayer
g_Tocador->~CSoundPlayer ();
return 1;
} // main().fim
int nSom = 0;
Esta variável indica qual efeito sonoro vai sofrer o comando aplicado.
int ntecla = 0;
Esta variável vai ser utilizada como um flag de aviso para indicar
quando ocorrer o pressionamento de uma tecla.
int nchar = 0;
Esta variável vai receber a tecla pressionada e vai ser utilizada
para discernir qual comando aplicar sobre o efeito sonoro.
iniciar(); menu();
Aqui a janela de console é inicializada e o menu é mostrado.
g_Tocador = new CSoundPlayer;
Aqui g_Tocador é inicializado como uma instância da classe
CSoundPlayer.
hJanela = GetConsoleWindow ();
g_Tocador->Initialise(hJanela);
Esse bloco de código pega o handle da janela de console e na sequência
inicializa o dispositivo de som.
g_Tocador->AddWav("drum1.wav", &nSom);
g_Tocador->AddWav("drum2.wav", &nSom);
Aqui é a forma de adicionar arquivos de efeitos sonoros na coleção
interna da classe. Repare que nSom é passado como referência para
receber a identificação numérica do som adicionado.
g_Tocador->PlaySoundA(0, false);
Aqui é a forma de tocar o efeito sonoro sem o loop ligado.
while(true) {
Com essa sintaxe o laço do while é eterno sendo quebrado pela letra
'Q' do teclado finalizando a aplicação.
ntecla = _kbhit();
Essa função examina se houve um pressionamento de tecla. (*) Em algum
momento da década de '90 as funções padrões da linguagem C ganharam
um underline o quê explica a nova aparência de _kbhit() e _getch().
if (ntecla != 0) nchar = _getch();
Nessa linha pegamos qual foi o caracter do teclado pressionando.
if(nchar == '1') nSom = 0;
if(nchar == '2') nSom = 1;
As teclas 1 e 2 identificam respectivamente qual som ficará em foco
para receber o controle aplicado.
if(nchar == '1') system("title prj_consoleSom: drum1.wav");
if(nchar == '2') system("title prj_consoleSom: drum2.wav");
Exibimos qual som está em foco na barra de títulos da janela.
if(nchar == 'P') g_Tocador->PlaySoundA(nSom, false);
if(nchar == 'p') g_Tocador->PlaySoundA(nSom, false);
Toca o som indicado.
if(nchar == 'S') g_Tocador->StopSound (nSom);
if(nchar == 's') g_Tocador->StopSound (nSom);
Pára o som que está sendo tocado
if(nchar == 'L') g_Tocador->PlaySoundA (nSom, true);
if(nchar == 'l') g_Tocador->PlaySoundA (nSom, true);
Toca o som continuamente (looping)
if(nchar == 'Q') break;
if(nchar == 'q') break;
Encerra a aplicação.
if(nchar != 'Q' ) nchar = 0;
ntecla = 0; } // endwhile
Limpa as variáveis de leitura do teclado para não ocorrer
comportamento não desejado.
g_Tocador->~CSoundPlayer ();
return 1;
Libera o objeto CSoundPlayer e finaliza a aplicação.
2.3 Inicialização da janela de console e apresentação do menu
Segue a listagem simples das funções iniciar() e menu(). Lembramos que
a função system() executa um comando de console ou programa do Windows.
void iniciar(void)
{
system("color f1");
printf("\n");
system("title prj_consoleSom");
} // iniciar().fim
O menu da aplicação
void menu(void)
{
printf (" \t 1- Seleciona drum1.wav \n");
printf (" \t 2- Seleciona drum2.wav \n\n");
printf (" \t P- Play ( Tocar ) \n");
printf (" \t S- Stop ( Parar ) \n");
printf (" \t L- Loop ( Tocar continuamente ) \n\n");
printf (" \t Q- Quit ( Sair ) \n\n");
} // menu().fim
3. Código fonte do projeto de exemplo:prj_consoleSom

//*******************************************************************
// Projeto: prj_consoleSom - Arquivo: motor.h
// Esta aplicação exemplifica como tocar um efeito sonoro
// contido em um arquivo do formato wav
// By www.toymaker.info \\ www.gameprog.com.br
//*******************************************************************
#ifndef motor_h
#define motor_h
// Exclui conteúdo raramente usado para agilizar compilação
#define WIN32_LEAN_AND_MEAN
// Funcionalidades do Windows
#include <windows.h>
// Funcionalidades da linguagem C
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <assert.h>
// Acesso multimídia
#include <mmsystem.h>
#include <dxerr9.h>
#include <dsound.h>
//-----------------------------------------------------------------------------
// Macros de utilidades
//-----------------------------------------------------------------------------
// Deleta memória alocada com new
#define SAFE_DELETE(p)
{ if(p)
{ delete (p); (p) =
NULL;
} }
// Deleta memória alocada com new em arrays
#define SAFE_DELETE_ARRAY(p)
{ if(p)
{ delete[] (p); (p) =
NULL;
} }
// Libera interfaces do directx
#define SAFE_RELEASE(p)
{ if(p)
{ (p)
->Release(); (p) =
NULL;
} }
#endif
//*******************************************************************
// SoundPlayer.h: Interface para a classe CSoundPlayer.
// Descrição: Usa o DirectSound para tocar sons
// Nota: Faz uso dos arquivos dsutil.cpp e dsutil.h que pertencem
// ao framework do directx. Estes arquivos definem as classes
// CSoundManager e CSound usadas por este programa
// Criado por Keith Ditchburn 30/01/2004
//*******************************************************************
#ifndef soundplayer_h
#define soundplayer_h
#include <vector>
// Classes utilizadas na implementação deste arquivo
class CSoundManager;
class CSound;
class CSoundPlayer
{
private:
// Objeto gerenciador de som
CSoundManager *m_soundManager;
// Coleção de arquivos de som
std::vector <CSound*> m_soundVector;
public:
// Adiciona um arquivo wav ao vetor produzindo um id
bool AddWav(char *filename, int *id);
// Toca o som com repetição continua se loop = TRUE
bool PlaySound(int id, bool loop);
// Pára de tocar o som
void StopSound(int id);
// Inicializa o objeto tocador de som (CSoundManager)
HRESULT Initialise(HWND hWnd);
// Construtor
CSoundPlayer();
// Destrutor
virtual ~CSoundPlayer();
};
#endif
// Projeto: prj_consoleSom - Arquivo: entrada.cpp
// Esta aplicação exemplifica como tocar um efeito sonoro
// contido em um arquivo do formato wav
// By www.toymaker.info \\ www.gameprog.com.br
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <windows.h>
#include <wincon.h>
#include "SoundPlayer.h"
// Inclusão das bibliotecas
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dsound.lib")
// Protótipo das funções
void iniciar(void);
void menu(void);
// Ponteiro para um objeto CSoundPlayer
CSoundPlayer *g_Tocador = NULL;
// Vamos precisar do handle da janela de console
HWND hJanela;
int main(void)
{
// Identifica qual efeito sonoro tocar
int nSom = 0;
// Controle de pressionamento das teclas
int ntecla = 0;
int nchar = 0;
// Inicializa a janela de console e mostra menu
iniciar();
menu();
// Cria um objeto CSoundPlayer
g_Tocador = new CSoundPlayer;
// Inicializa o gerenciador de som
hJanela = GetConsoleWindow ();
g_Tocador->Initialise(hJanela);
// Adiciona efeitos sonoros na fila
g_Tocador->AddWav("drum1.wav", &nSom);
g_Tocador->AddWav("drum2.wav", &nSom);
// Toca um som inicialmente
g_Tocador->PlaySoundA(0, false);
// Pressione a letra 'Q' para quebrar o laço
while(true)
{
ntecla = _kbhit();
if (ntecla != 0) nchar = _getch();
// Indicação de qual id de som para manipular
if(nchar == '1') nSom = 0;
if(nchar == '2') nSom = 1;
// Mostra o som sendo manipulado na janela
if(nchar == '1') system("title prj_consoleSom: drum1.wav");
if(nchar == '2') system("title prj_consoleSom: drum2.wav");
// Toca o som indicado
if(nchar == 'P') g_Tocador->PlaySoundA(nSom, false);
if(nchar == 'p') g_Tocador->PlaySoundA(nSom, false);
// Pára o som que está sendo tocado
if(nchar == 'S') g_Tocador->StopSound (nSom);
if(nchar == 's') g_Tocador->StopSound (nSom);
// Toca o som continuamente (looping)
if(nchar == 'L') g_Tocador->PlaySoundA (nSom, true);
if(nchar == 'l') g_Tocador->PlaySoundA (nSom, true);
// Encerra a aplicação
if(nchar == 'Q') break;
if(nchar == 'q') break;
// Limpa as variáveis de leitura do teclado
if(nchar != 'Q' ) nchar = 0;
ntecla = 0;
} // endwhile
// Libera o objeto CSoundPlayer
g_Tocador->~CSoundPlayer ();
return 1;
} // main().fim
void iniciar(void)
{
system("color f1");
printf("\n");
system("title prj_consoleSom");
} // iniciar().fim
void menu(void)
{
printf (" \t 1- Seleciona drum1.wav \n");
printf (" \t 2- Seleciona drum2.wav \n\n");
printf (" \t P- Play ( Tocar ) \n");
printf (" \t S- Stop ( Parar ) \n");
printf (" \t L- Loop ( Tocar continuamente ) \n\n");
printf (" \t Q- Quit ( Sair ) \n\n");
} // menu().fim