sexta-feira, 18 de maio de 2012

paSDLAudio e paSDLMusic

Olá leitor!

Após um certo tempo sem postar muitos avanços, venho hoje falar sobre como foi o desenvolvimento inicial da parte de som da engine. Primeiramente eu precisava reunir informações a respeito de como implementaria essa parte da engine, quais funções utilizaria, quais efeitos exploraria e qual seria o sistema de classes.

Após um estudo entre a SDL_Audio e a SDL_Mixer, preferi a Mixer, devido às suas funções mais aprimoradas. Assim, planejei dividir a engine de som em três partes:

Classe paSDLAudio: Responsável por inicializar a SDL_Mixer, bem como oferecer as constantes e ajustar parâmetros globais para as demais classes de som (ex: volume para Música e Efeitos).

Classe paSDLMusic: Responsável por gerar instâncias com métodos envolvendo a reprodução de músicas, bem como os parâmetros das músicas do jogo.

Classe paSDLSoundEffects: Responsável por gerar instâncias com métodos envolvendo a reprodução de efeitos, bem como do gerenciamento de seus canais e os parâmetros dos efeitos no jogo.


Neste post, irei abordar o desenvolvimento das duas primeiras classes, envolvendo o que deu certo e as centenas de coisas que deram errado (rsrsrsrsrsrs).

Iniciando o desenvolvimento da paSDLAudio, decidi por fazê-la oferecer uma preparação para o audio no jogo, bem como o suporte para as funcionalidades das outras classes de som. Por enquanto, essa classe possui somente a configuração da frequência do audio e da saída mono ou stereo.

O maior problema foi com a paSDLMusic. Primeiramente, planejei a classe para que essa contesse métodos para inicializar os atributos da música na classe, bem como seu conteúdo (do tipo Mix_Music). Os principais métodos da classe que desenvolvi ficaram totalmente funcionais. São eles:

1- reload: Recarregar a música, passando um novo endereço.

2- play: Tocar a música, passando o número de vezes que a mesma repetirá e o número de segundos do FadeIn. Os valores padrão são infinitos loops para o loop, e 0 para o tempo.

3- stop: Para a música. Recebe um tempo em segundos identificando o FadeOut. Por padrão é 0.

4- set/getVolumeMusics: Retorna e recebe o volume das músicas (alterando este parâmetro, toda os volumes das músicas são alterados).

5- isPlaying / isPaused / isStoped: Retorna o estado atual dos casos especificados.

6- pause: Pausa a música.

7- restart: Reinicia a música (sem fadeIn, mas reinicia o número de loops inseridos no primeiro play).


A maior dificuldade na criação dos métodos acima foi sincronizá-los de forma a oferecer uma boa estrutura anti-usuário. Por exemplo, o que aconteceria se eu apertasse pause e depois stop? Com o retorno padrão da SDL_Mixer, pause continuaria verdadeiro. Outro problema foi a ausência do estado stop, tendo eu que criá-lo baseando nos resultados das funções Mix_PlayingMusic() e Mix_PausedMusic().


Parece terminado, certo? Errado! Ainda falta estabelecer uma forma de comunicar o usuário quando a música termina, além de acrescentar a um contador o número total de músicas que ja foram criadas, para não exceder um certo limite (8 no caso).

Para tal, tive que utilizar a função Mix_HookMusicFinished, a qual recebe um ponteiro para uma função que será chamada quando a música finalizar.
A minha primeira tentativa de resolver o problema foi passar um ponteiro para um método, cuja sintaxe foi descrevida pelo autor que estava lendo o tutorial como "temida até pelos experts em programação avançada de ponteiros". Consegui programar o ponteiro de métodos, mas infelizmente a função só recebe realmente ponteiros de funções.

Meu segundo passo foi criar uma função do tipo friend, a qual recebesse uma referência a um objeto da classe para que eu pudesse modificar as variáveis privadas quando o fim da música fosse chamado. O grande problema é que a tal função também não aceita apontar para funções que recebem parâmetros.

Sem sucesso nas tentativas, comecei a tentar pelo caminho dos métodos e variáveis estáticas, já que estes podem ser acessados facilmente em qualquer lugar. Então, descobri como utilizar métodos estáticos, e também como fazer a misteriosa declaração de variáveis estáticas. Para declará-las com sucesso, foi necessário definí-las no escopo privado da classe e no topo do arquivo cpp.

Feitas todas as modificações necessárias dentro de todos os métodos da classe para substituir algumas variáveis e métodos para estáticos, tudo parecia correr bem, quando outro problema surge: Em caso de loop, a SDL_Mixer só chama a função quando todos os loops tiverem sido rodados, o que estragava completamente boa parte do que tinha feito.
A solução que encontrei foi gerenciar a reprodução uma a uma, mandando reproduzir uma vez a cada vez que a música acabasse, até um contador atingir o número total de loops desejados. O problema dessa abordagem foi que estabeleci praticamente a maior responsabilidade de gerenciamento para a função passada como parâmetro em Mix_HookMusicFinished. Isso se tornou um problema, pois estava perdendo o controle dos estados da música (pausada, parada, reproduzindo, finalizada).

Lá vou eu mais uma vez. Após resolver todos estes pepinos, surge outro problema: Acabei tendo a necessidade de definir o endereço das músicas como estático para que a função tivesse acesso. Isso feria completamente o sentido da classe, uma vez que cada objeto é uma música. Se a variável contendo as músicas for estática, então não existem objetos com músicas diferentes.
Tentei resolver esse problema aplicando um friend entre as classes paSDLAudio e paSDLMusic (hahaha, aprendi muitas coisas novas com essa maldita classe de som) para ter acesso a variáveis privadas de gerenciamento, e criei um vetor boleano que sinalizasse se a posição do vetor de músicas estava ocupada. Então, percorria-se todo o vetor em um for buscando posições false, e a primeira encontrada era atribuída como ID para aquela música.

Parece genial, mas é estúpido, pois para acessar a música certa eu também precisaria deixar o ID como estático rsrsrsrs... Nesse ponto, a brincadeira ja estava começando a perder a graça...

Ainda tentei, ao invés de usar a variável estático com o nome da música para reprodução, um ponteiro estático para a música atual, mas já estava tendo sérios problemas com os principios do KISS e YAGNI para fazer algo que simplesmente poderia ser resolvido facilmente pelo usuário...

Ou seja, se o usuário quer controlar quando a música termina, basta que ele mande a música ser reproduzida apenas uma vez usando play, e, quando isStoped() retornasse true, o usuário reiniciasse a reprodução usando restart(). É, as vezes uma funcionalidade a menos resolve todos os problemas...

Apesar dos problemas, consegui exercitar três conceitos que não utilizava (métodos e variáveis estáticas para classes, friend de funções e classes e ponteiros para funções e métodos). Também me rendeu uma boa refatoração, e centenas de testes da classe. No fim deu tudo certo!


Pretendo continuar implementando a parte de som, agora com foco nos SoundEffects.

Até lá!

Nenhum comentário:

Postar um comentário