0x03 Ensamblador ZX Spectrum OXO – sonido
En esta entrega de Ensamblador ZX Spectrum OXO vamos a implementar distintos sonidos, y los vamos a usar para temporizar haciendo pausas entre distintos eventos.
Tabla de contenidos
Sonidos
Creamos el archivo Sound.asm y añadimos el include en Main.asm.
En Sound.asm vamos añadir la definición de los distintos sonidos y melodías.
; -------------------------------------------------------------------
; Sonidos.
;
; Los sonidos acaban en $00, byte que indica el final.
; -------------------------------------------------------------------
; Cuenta atrás
SoundCountDown:
db $03, $8c, $3a, $00
; Error
SoundError:
db $0d, $c6, $1e, $16, $13, $13, $00
; Perdida de movimiento
SoundLostMovement:
db $0d, $07, $10, $0b, $96, $12, $0a, $4d, $14
db $0b, $96, $12, $1a, $2c, $08, $00
; Siguiente jugador
SoundNextPlayer:
db $01, $9d, $7b, $00
; Movimiento Spectrum
SoundSpectrum:
db $06, $6e, $20, $06, $6e, $20, $05, $b7, $24
db $06, $6e, $20, $05, $13, $29, $05, $b7, $24
db $06, $6e, $20, $00
; Tablas
SoundTie:
db $0d, $07, $10, $0b, $96, $12, $06, $d4, $1e
db $0b, $96, $12, $07, $a6, $1b, $0b, $96, $12
db $08, $a5, $18, $0b, $96, $12, $09, $b4, $15
db $0b, $96, $12, $0a, $4d, $14, $0b, $96, $12
db $0d, $07, $10, $00
; Punto
SoundWinGame:
db $06, $6e, $20, $05, $13, $29, $04, $40, $30
db $06, $6e, $20, $05, $13, $29, $04, $40, $30
db $06, $6e, $20, $05, $13, $29, $04, $c7, $2b
db $05, $13, $29, $05, $b7, $24, $05, $13, $29
db $06, $6e, $20, $05, $13, $29, $04, $40, $30
db $06, $6e, $20, $05, $13, $29, $04, $40, $30
db $06, $6e, $20, $05, $13, $29, $04, $c7, $2b
db $05, $13, $29, $05, $b7, $24, $06, $6e, $20
db $00
Todos los sonidos tienen como último byte cero, que vamos a usar en la rutina que los reproduce para saber cuando se acaban.
En estas definiciones cada nota está compuesta por tres bytes en lugar de cuatro, los dos primeros son la nota y el tercero el byte bajo de la duración, siendo todas fusas y/o semifusas.
Rutina musical
Implementamos la rutina que reproduce los sonidos.
; -------------------------------------------------------------------
; Reproduce una melodía o sonido.
;
; Entrada: BC = Dirección de inicio de la melodía.
;
; Para ahorrar se hace byte a byte, siendo la duración siempre fusa o
; semifusa; sólo se necesita un byte para la duración.
; Esta rutina está adaptada para este funcionamiento.
; -------------------------------------------------------------------
PlayMusic:
push af
push bc
push de
push hl
push ix ; Preserva registros
playMusic_loop:
ld a, (bc) ; A = byte alto nota
ld h, a ; H = A
or a ; ¿A = 0?
jr z, playMusic_end ; A = 0, fin
inc bc ; BC = siguiente valor
ld a, (bc) ; A = byte bajo nota
ld l, a ; L = A
inc bc ; BC = siguiente valor
ld a, (bc) ; A = duración nota
ld e, a ; E = A
ld d, $00 ; D = 0 (fusa o semifusa)
inc bc ; BC = siguiente valor
push bc ; Preserva BC
call BEEPER ; Reproduce nota
pop bc ; Recupera BC
jr playMusic_loop ; Bucle hasta que llegue al 0
; fin melodía
playMusic_end:
pop ix
pop hl
pop de
pop bc
pop af ; Recupera registros
ret
PlayMusic recibe en BC la dirección del sonido a emitir, carga la nota en HL, la duración en DE, la emite y sigue en bucle hasta que se llegue al byte cero, que marca el fin del sonido. Sólo usa un byte para la duración, siendo D siempre cero.
En Main.asm vamos a incluir las llamadas necesarias para que se emitan los sonidos.
Localizamos la etiqueta Loop y borramos desde LD B, $19 hasta DJNZ loop_wait, ya que vamos a temportizar con el sonido. Más abajo, tras CALL PrintPoints, añadimos el sonido de siguiente jugador:
ld bc, SoundNextPlayer
call PlayMusic
Localizamos la etiqueta loop_print y justo por encima la línea JR Loop. Encima de esta línea añadimos el sonido de error:
ld bc, SoundError
call PlayMusic
Localizamos la etiqueta loop_win y justo por debajo la línea INC (HL). Debajo de esta línea añadimos el sonido de ganador de punto:
ld bc, SoundWinGame
call PlayMusic
Localizamos la etiqueta loop_reset y justo por encima añadimos el sonido de tablas.
ld bc, SoundTie
call PlayMusic
Compilamos, cargamos en el emulador y oímos cada uno de los sonidos.
Main
El tamaño de Main.asm ha crecido considerablemente desde que empezamos, por lo que es hora de ver el aspecto que debe tener una vez comentado.
org $5e88 ; Dirección de carga
Main:
ld hl, Sprite_P1 ; HL = dirección Sprite_P1
ld (UDG), hl ; UDG = dirección Sprite_p1
ld a, $02 ; A = 2
call OPENCHAN ; Abre canal 2
xor a ; A = 0, Z = 0, C = 0
call BORDER ; Cambia borde
call CLA ; Cambia atributos pantalla
call CLS ; Borra pantalla
Init:
call PrintBoard ; Pinta tablero
ld hl, Name_p1 ; HL = nombre jugador 1
ld de, player1_name ; DE = nombre jugador 1
ld bc, LENNAME ; BC = longitud nombre
ldir ; Pasa datos
ld hl, Name_p2 ; HL = nombre jugador 2
ld de, player2_name ; DE = nombre jugador 2
ld bc, LENNAME ; BC = longitud nombre
ldir ; Pasa datos
call PrintInfo ; Pinta info
ld bc, LENDATA ; BC = longitud datos partida
call ResetValues ; Limpia datos
Loop:
ld hl, TitleTurn ; HL = dirección turno
ld de, TitleTurn_name ; DE = dirección nombre en turno
call DoMsg ; Compone y pinta turno
call PrintPoints ; Pinta puntos
ld bc, SoundNextPlayer ; BC = dirección sonido
call PlayMusic ; Emite sonido
loop_key:
call WaitKeyBoard ; Espera pulsación tecla
ld a, c ; A = C
cp KEY0 ; ¿Pulsada tecla?
jr z, loop_key ; No, bucle
call ToMove ; Comprueba movimiento
jr z, loop_print ; Correcto, salta
ld hl, TitleError ; HL = dirección error
call PrintMsg ; Pinta error
ld bc, SoundError ; BC = dirección sonido
call PlayMusic ; Emite sonido
jr Loop ; Bucle principal
loop_print:
call PrintOXO ; Pinta ficha
loop_checkWinner:
call ChekcWinner ; ¿Algún ganador?
jr nz, loop_tie ; No, comprueba tablas
call PrintWinnerLine ; Pinta línea ganadora
ld hl, TitlePointFor ; HL = dirección ganador
ld de, TitlePointName ; HL = dirección nombre ganador
call DoMsg ; Compone y pinta ganador
ld hl, Points_p1 ; HL = dirección puntos jugador 1
ld a, (PlayerMoves) ; A = jugador que ha movido
or a ; ¿Jugador 1?
jr z, loop_win ; Sí, salta
inc hl ; HL = dirección puntos jugador 2
loop_win:
inc (hl) ; Incrementa puntuación
ld bc, SoundWinGame ; BC = dirección sonido
call PlayMusic ; Emite sonido
jr loop_reset ; Salta
loop_tie:
ld hl, MoveCounter ; HL = dirección contador movimientos
inc (hl) ; Incrementa contador
ld a, $09 ; A = 9
cp (hl) ; ¿Contador = 9?
jr nz, loop_cont ; No, salta
ld hl, Points_tie ; HL = dirección puntos tablas
inc (hl) ; Incrementa puntuación
ld hl, TitleTie ; HL = dirección tablas
call PrintMsg ; Pinta tablas
ld bc, SoundTie ; BC = dirección sonido
call PlayMusic ; Emite sonido
loop_reset:
ld bc, LENDATA-$04 ; BC = longitud datos limpiar
call ResetValues ; Limpia datos
call PrintBoard ; Pinta tablero
loop_cont:
ld a, (PlayerMoves) ; A = jugador que ha movido
xor $01 ; A = jugador siguiente
ld (PlayerMoves), a ; Actualiza en memoria
jr Loop ; Bucle principal
include "Game.asm"
include "Rom.asm"
include "Screen.asm"
include "Sprite.asm"
include "Sound.asm"
include "Var.asm"
end Main
Ensamblador ZX Spectrum, conclusión
Tenemos desarrollado y funcional gran parte del juego. Podemos jugar nuestras primeras partidas con amigos y/o familiares, y tenemos sonido.
En el próximo capítulo implementaremos las distintas opciones de la partida, y el fin de la misma.
Todo el código que hemos generado lo podéis descargar desde aquí.
Enlaces de interés
Ensamblador para ZX Spectrum OXO por Juan Antonio Rubio García.
Esta obra está bajo licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional License.
También puedes visitar el resto de tutoriales:
Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.