Curso completo de DirectX 9 com C\C++
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
Fase 08-1

index << >>


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

index << >>

Produzido por Gameprog: Jair Pereira - Agosto/2014 © gameprog.br@gmail.com http://www.gameprog.com.br