Efectos de sonido – parte 2

Con la teoría aprendida en el post anterior vamos a encarar el tema de los efectos sonoros. Vamos a realizar 2 sonidos: el disparo del jugador y la explosión al recibir el mismo.
Comenzaremos explicando el efecto de disparo, al ser de mayor complejidad de implementar. En primer lugar vamos a agregar las constantes de los registros del SID que vamos a utilizar (en vars.asm):

.const sidPtr    = 54272
.const sid_vol = 54296
.const sid_hfreq1 = 54273
.const sid_ad1 = 54277
.const sid_sr1 = 54278
.const sid_wave1 = 54276

Y luego vamos a inicializar los registros del SID en initvars.asm:

          
          ldy #0
          lda #0
loop:                     // reseteamos los registros
          sta sidPtr,y    
          iny 
          cpy #25
          bne loop

          // configuramos canal 1
          ldx #16          // volumen maximo 
          stx sid_vol
          ldx #0           // attack y decay en 0
          stx sid_ad1
          ldx #100
          stx sid_hfreq1   
          ldx #15*16+5     // volumen de sustain 15
          stx sid_sr1      // relax 5

Disparos

Para el sonido del disparo vamos a hacer un sonido que comenzará en una frecuencia alta, y en cada frame va disminuyendo la frecuencia hasta llegar a 0 (el típico sonido de disparos de naves). Para ello vamos a agregar un nuevo archivo que llamaremos sound_fire1.asm y lo importaremos desde main.asm inmediatamente después de importar animatePlayer1.asm.

Por que es importante esto? Bueno, porque vamos a utilizar la variable fire1 (que se pone en 1 cuando disparamos) y si colocamos el import luego de procesar el disparo nos vamos a encontrar que fire1 siempre esta en 0, ya que la parte que procesa el disparo lo pone en 0…

El código es muy simple: primero chequeamos que se haya producido un disparo, si es así ponemos el MSB de la frecuencia en 100, y comenzamos el sonido. Luego en cada frame vamos a ver si la frecuencia es distinta de 0, si es así decrementamos en 1, y cuando llega a 0 lo apagamos. El código, a continuación:

ldx fire1           
    cpx #1              // si se pulso disparo
    beq turn_on_fire1   // lo enciendo
    jmp dec_freq_1      // o sigo de largo
    
turn_on_fire1:    
    ldx #100             // inicializo la frecuencia
    stx sound_fire1_freq 
    ldx #17              // inicio el sonido
    stx sid_wave1        // con onda triangular
    
dec_freq_1:     
    ldx sound_fire1_freq 
    cpx #0               // comparo frec por 0
    beq turn_off_fire1   // si es igual lo apago
    
    stx sid_hfreq1       // y si no 
    dec sound_fire1_freq 
    dec sound_fire1_freq // lo decremento
    jmp exit
    
turn_off_fire1:    
    ldx #16
    stx sid_wave1
    
exit:

Explosiones

Este sonido es mucho mas simple, ya que solamente implica encenderlo y apagarlo. Vamos a usar el canal 3 del SID, con la siguiente configuración (en initvars.asm):

          // configuramos canal 3
          ldx #0
          stx sid_ad3    // attack/decay rapido
          ldx #3        
          stx sid_hfreq3 // frecuencia muy grave
          ldx #15*16+9   // volumen sustain max
          stx sid_sr3    // relax medio (9)

Poner relajación en 9 hace que cuando prendemos y apagamos el sonido va desvaneciéndose lentamente (mas alto el número, mas duración del sonido).
Luego, para simplificar y embellecer el código vamos a escribir una macro, a la que le pasaremos la frecuencia. De esta manera podremos utilizar diferentes sonidos de explosiones (ejemplo, si quisieramos una explosión diferente para cuando reventamos un tanque de combustible).

.macro playNoise(freq) {
    ldx #freq
    stx sid_hfreq3
    ldx #129
    stx sid_wave3
    ldx #128
    stx sid_wave3
}

Luego utilizaremos la macro donde detectamos que se produce una colisión (en detectCollision.asm) simplemente llamandola con playNoise(2)

Y esto es todo por hoy… y por el curso. Desde que inicié el tutorial a principio del año (con el efecto ciclado de colores) recorrimos un largo camino. Personalmente fue muy gratificante escribir este curso ya que me permitió aprender mucho mas de la C64/6502, además de mejorar como programador, redactor y educador.

A continuación, les dejo índice del curso. Además el enlace al proyecto en Github, mi página en Facebook, y mi Linkedin.

Índice del Curso

Proyecto en Github

Página personal en Facebook

Linked in

Más adelante tengo planeado hacer pequeños tutoriales acerca de programación de efectos, demos, gráficos y lo que se me ocurra sobre esta maravillosa maquina. Además quiero escribir un pequeño curso sobre como programar un motor gráfico para videojuegos en Javascript.

Nos vemos mas adelante!

Anuncios

Efectos de sonido – mini teoría

Para darle un cierre al tutorial, hoy vamos a abordar el tema de los efectos sonoros. Personalmente es un tema que me costó bastante de entender, por lo que decidí simplificar lo mas posible el apartado sonoro.
Primero, vamos a ver que es lo que logramos:

Un poco de teoría

El sonido en la C64 es gestionado por un chip, el famoso SID 6581. Este cuenta con 3 canales independientes, cada uno de ellos posee 4 tipos de ondas diferentes (triangular, diente de sierra, cuadrada/pulsos, ruido blanco), generador de envolvente ADSR, filtros, y unas cuantas cosas mas que no entendí. Para los efectos que vamos a hacer, con esto tenemos mas que suficiente, si queremos profundizar podemos ver los todos los registros en https://www.c64-wiki.com/wiki/SID, o algo mas avanzado en http://codebase64.org/doku.php?id=base:sid_programming.

Los osciladores

Cada canal puede generar 4 ‘tipos’ de sonidos básicos (no se me ocurre como decirlo – si alguien sabe por favor comenten):

  • Triangular, con un sonido suave, como si fuese una flauta
  • Diente de sierra: mas agresivo, mucho mas timbrado… quizá como un violín?
  • Cuadrada/pulsos: Mucho mas agresivo?? no lo sé, no logré hacerlo sonar con esta forma de onda
  • Ruido Blanco: Básicamente ruido, ideal para explosiones, etc

Todos estos sonidos básicos puede luego modificarse en frecuencia, envolvente, filtros…

Envolventes

Los generadores de envolventes modelan la amplitud del sonido desde el momento en que se le da la orden de tocar una nota hasta que le damos la orden de apagarla. ADSR son las iniciales de Attack, Decay, Sustain y Release, y lo que especificamos es el tiempo que tarda la nota en llegar a máximo volumen desde que la disparamos (attack), el tiempo que tarda desde ese máximo hasta el volumen Sustain (Decay), el volumen que queremos que suene (sustain) y el tiempo que queremos que dure desde que damos la orden de apagar la nota hasta el volumen cero. Más claro en el siguiente grafico:

Y… esto como funciona?

Para generar un sonido primero tenemos que configurar algunos registros del SID. Antes que nada no está de más ponerlos todos en 0, desde la posición 54272 hasta 54296 (por las dudas que tengan basura seteada). Luego tenemos que poner el volumen general al máximo, utilizando la posición de memoria 54296, configurar el ADSR (en este caso para el canal 1), con las posiciones de memoria 54277 (Attack/Decay) y 54278 (Release/Sustain), establecer la frecuencia con las posiciones 54272 y 54273, para RECIÉN ahí poder disparar la nota, con la posición 54276… Ufff!, mejor un código de ejemplo en BASIC, que dispara un sonido sencillo:

10 for l = 54272 to 54296 : poke l,0 : next
20 poke 54296, 15 : rem volumen maximo
30 poke 54273, 100 : rem frecuencia MSB
40 poke 54272, 0 : rem frecuencia LSB
50 poke 54277,  8*16 + 10 : rem atack 8 - decay 10
60 poke 54278,  2*16 + 12 : rem sustain 2 -  release 12
70 poke 54276, 32 + 1 : rem onda triangular, comienza
71 print"comienza sonido"
75 for pause = 0 to 2500: next : rem pausa 4 segundos aprox
80 poke 54276, 32 : rem apaga sonido
81 print"finaliza sonido"

si ejecutamos este programita veremos que inicia un sonido desde un volumen 0, sube rápidamente al máximo, luego baja a un volumen mínimo, y se queda en ese volumen por aproximadamente 4 segundos, hasta que comienza a desvanecerse hasta desaparecer (noten que aparece el cartel “FINALIZA SONIDO”, pero es en ese momento en que comienza a desvanecerse, aquí esta el tiempo del parámetro Release).
El ejemplo está comentado para que se comprenda fácilmente, pero quisiera dedicarle un par de lineas a explicar como se establece la frecuencia (el tono), como configuramos el ADSR, y como disparamos/apagamos el sonido.
El Attack/Sustain se especifica en la dirección de memoria 54277, utilizandose los 4 bits superiores para el Attack y los 4 bits inferiores para el Sustain, por eso en el ejemplo lo escribo de esa forma, para que se entienda mejor que es lo que estoy configurando (8*16 seteo los 4 bits superiores, y adicionando un valor de 0 a 15 especifico los 4 inferiores). Todo esto aplica también para Sustain/Release.
La frecuencia se programa con 2 bytes, ya que no nos alcanzaría una sola posición de memoria para cubrir todo el rango audible. El cálculo no lo tengo correctamente aquí, pero para lo que necesitamos en este tutorial vamos a dejar el LSB (54272) en cero, y vamos a toquetear el HSB (54273) para cambiar el tono. Si queremos profundizar en este tema recomiendo ir a http://codebase64.org/doku.php?id=base:how_to_calculate_your_own_sid_frequency_table, si clickean en el enlace comprobarán que es realmente complicado especificar correctamente una frecuencia.
Finalmente queda ver como establecemos la forma de onda y comenzamos el sonido. La posicion 54276 establece con los 4 bits superiores que forma de onda queremos utilizar, y con el bit 0 si queremos que suene o se apague. Por eso comenzamos un sonido con POKE 54276,32 + 1, y lo apagamos con POKE 54276,32.

Por ahora suficiente de teoría, con el material aquí proporcionado podemos generar los sonidos de los lasers cuando disparan, las explosiones de los jetpacs y los Fuels, y un sonido simple para cuando agarramos combustible… pero eso para el próximo post. ̉̉Nos vemos!