0x0C Ensamblador ZX Spectrum Marciano – Sonido
En este capítulo de Ensamblador ZX Spectrum Marciano vamos a implementar la música. A diferencia de la versión original de Batalla Espacial, en esta ocasión vamos a incluir música durante la partida, además de los efectos de sonido que ya teníamos.
Creamos la carpeta Paso12 y copiamos desde la carpeta Paso11 los archivos Cargador.tap, Const.asm, Ctrl.asm, Game.asm, Graph.asm, Int.asm, Main.asm, make o make.bat, Print.asm y Var.asm.
Tabla de contenidos
- Probando, probando
- Ritmo y compás
- Distintos ritmos
- Ensamblador ZX Spectrum, conclusión
- Enlaces de interés
Probando, probando
Antes de implementar el sonido en nuestro juego, vamos a realizar una serie de pruebas para ver como lo hacemos.
Dentro de la carpeta Paso12 vamos a crear la carpeta Sound, dentro de la misma vamos a ir añadiendo los archivos de código de las pruebas que vamos a realizar.
Creamos, dentro de Sound, el archivo Const.asm para añadir las constantes que nos van a hacer falta, empezando por la dirección de memoria de la rutina BEEPER de la ROM, rutina que hace sonar las notas que enviemos.
; -----------------------------------------------------------------------------
; Rutina beeper de la ROM.
;
; Entrada: HL -> Nota.
; DE -> Duración.
;
; Altera el valor de los registros AF, BC, DE, HL e IX.
; -----------------------------------------------------------------------------
BEEP: EQU $03b5
Podemos observar en los comentarios que esta rutina recibe en el registro HL la nota, y en DE la duración (frecuencia).
Lo siguiente que vamos a añadir al archivo son las notas que, al igual que las frecuencias, obtuve en Programandala, y podéis descargar desde aquí.
; -----------------------------------------------------------------------------
; Notas a cargar en HL
; -----------------------------------------------------------------------------
C_0: EQU $6868
Cs_0: EQU $628d
D_0: EQU $5d03
Ds_0: EQU $57bf
E_0: EQU $52d7
F_0: EQU $4e2b
Fs_0: EQU $49cc
G_0: EQU $45a3
Gs_0: EQU $41b6
A_0: EQU $3e06
As_0: EQU $3a87
B_0: EQU $373e
C_1: EQU $3425
Cs_1: EQU $3134
D_1: EQU $2e6f
Ds_1: EQU $2bd3
E_1: EQU $295c
F_1: EQU $2708
Fs_1: EQU $24d5
G_1: EQU $22c2
Gs_1: EQU $20cd
A_1: EQU $1ef4
As_1: EQU $1d36
B_1: EQU $1b90
C_2: EQU $1a02
Cs_2: EQU $188b
D_2: EQU $1728
Ds_2: EQU $15da
E_2: EQU $149e
F_2: EQU $1374
Fs_2: EQU $125b
G_2: EQU $1152
Gs_2: EQU $1058
A_2: EQU $0f6b
As_2: EQU $0e9d
B_2: EQU $0db8
C_3: EQU $0cf2
Cs_3: EQU $0c36
D_3: EQU $0b86
Ds_3: EQU $0add
E_3: EQU $0a40
F_3: EQU $09ab
Fs_3: EQU $091e
G_3: EQU $089a
Gs_3: EQU $081c
A_3: EQU $07a6
As_3: EQU $0736
B_3: EQU $06cd
C_4: EQU $066a
Cs_4: EQU $060c
D_4: EQU $05b3
Ds_4: EQU $0560
E_4: EQU $0511
F_4: EQU $04c6
Fs_4: EQU $0480
G_4: EQU $043d
Gs_4: EQU $03ff
A_4: EQU $03c4
As_4: EQU $038c
B_4: EQU $0357
C_5: EQU $0325
Cs_5: EQU $02f7
D_5: EQU $02ca
Ds_5: EQU $02a0
E_5: EQU $0279
F_5: EQU $0254
Fs_5: EQU $0231
G_5: EQU $020f
Gs_5: EQU $01f0
A_5: EQU $01d3
As_5: EQU $01b7
B_5: EQU $019c
C_6: EQU $0183
Cs_6: EQU $016c
D_6: EQU $0156
Ds_6: EQU $0141
E_6: EQU $012d
F_6: EQU $011b
Fs_6: EQU $0109
G_6: EQU $00f8
Gs_6: EQU $00e9
A_6: EQU $00da
As_6: EQU $00cc
B_6: EQU $00bf
C_7: EQU $00b2
Cs_7: EQU $00a7
D_7: EQU $009c
Ds_7: EQU $0091
E_7: EQU $0087
F_7: EQU $007e
Fs_7: EQU $0075
G_7: EQU $006d
Gs_7: EQU $0065
A_7: EQU $005e
As_7: EQU $0057
B_7: EQU $0050
C_8: EQU $004a
Cs_8: EQU $0044
D_8: EQU $003e
Ds_8: EQU $0039
E_8: EQU $0034
F_8: EQU $0030
Fs_8: EQU $002b
G_8: EQU $0027
Gs_8: EQU $0023
A_8: EQU $0020
As_8: EQU $001c
B_8: EQU $0019
La nomenclatura usada se basa en la notación anglosajona, siendo:
Do | C |
Re | D |
Mi | E |
Fa | F |
Sol | G |
La | A |
Si | B |
La ese minúscula que sigue a algunas notas indica que es sostenida, el número es la escala. Si el número es menor, el sonido es más grave, por el contrario, si es mayor, el sonido es más agudo.
Lo siguiente a añadir son las frecuencias, para las que hemos usado la misma notación, pero añadiendo _f para distinguirlas de las notas. Las frecuencias aquí expuestas hacen que las notas suenen durante un segundo, por lo que si las dividimos entre dos sonarían durante medio segundo. Tened en cuenta que mientras emitimos un sonido nuestro programa se va a parar, es por eso que vamos a dividir las frecuencias entre treinta y dos.
; -----------------------------------------------------------------------------
; Frecuencias a cargar en DE, 1 segundo ( / 2 = 0.5 ....)
; -----------------------------------------------------------------------------
C_0_f: EQU $0010 / $20
Cs_0_f: EQU $0011 / $20
D_0_f: EQU $0012 / $20
Ds_0_f: EQU $0013 / $20
E_0_f: EQU $0014 / $20
F_0_f: EQU $0015 / $20
Fs_0_f: EQU $0017 / $20
G_0_f: EQU $0018 / $20
Gs_0_f: EQU $0019 / $20
A_0_f: EQU $001b / $20
As_0_f: EQU $001d / $20
B_0_f: EQU $001e / $20
C_1_f: EQU $0020 / $20
Cs_1_f: EQU $0022 / $20
D_1_f: EQU $0024 / $20
Ds_1_f: EQU $0026 / $20
E_1_f: EQU $0029 / $20
F_1_f: EQU $002b / $20
Fs_1_f: EQU $002e / $20
G_1_f: EQU $0031 / $20
Gs_1_f: EQU $0033 / $20
A_1_f: EQU $0037 / $20
As_1_f: EQU $003a / $20
B_1_f: EQU $003d / $20
C_2_f: EQU $0041 / $20
Cs_2_f: EQU $0045 / $20
D_2_f: EQU $0049 / $20
Ds_2_f: EQU $004d / $20
E_2_f: EQU $0052 / $20
F_2_f: EQU $0057 / $20
Fs_2_f: EQU $005c / $20
G_2_f: EQU $0062 / $20
Gs_2_f: EQU $0067 / $20
A_2_f: EQU $006e / $20
As_2_f: EQU $0074 / $20
B_2_f: EQU $007b / $20
C_3_f: EQU $0082 / $20
Cs_3_f: EQU $008a / $20
D_3_f: EQU $0092 / $20
Ds_3_f: EQU $009b / $20
E_3_f: EQU $00a4 / $20
F_3_f: EQU $00ae / $20
Fs_3_f: EQU $00b9 / $20
G_3_f: EQU $00c4 / $20
Gs_3_f: EQU $00cf / $20
A_3_f: EQU $00dc / $20
As_3_f: EQU $00e9 / $20
B_3_f: EQU $00f6 / $20
C_4_f: EQU $0105 / $20
Cs_4_f: EQU $0115 / $20
D_4_f: EQU $0125 / $20
Ds_4_f: EQU $0137 / $20
E_4_f: EQU $0149 / $20
F_4_f: EQU $015d / $20
Fs_4_f: EQU $0172 / $20
G_4_f: EQU $0188 / $20
Gs_4_f: EQU $019f / $20
A_4_f: EQU $01b8 / $20
As_4_f: EQU $01d2 / $20
B_4_f: EQU $01ed / $20
C_5_f: EQU $020b / $20
Cs_5_f: EQU $022a / $20
D_5_f: EQU $024b / $20
Ds_5_f: EQU $026e / $20
E_5_f: EQU $0293 / $20
F_5_f: EQU $02ba / $20
Fs_5_f: EQU $02e4 / $20
G_5_f: EQU $0310 / $20
Gs_5_f: EQU $033e / $20
A_5_f: EQU $0370 / $20
As_5_f: EQU $03a4 / $20
B_5_f: EQU $03db / $20
C_6_f: EQU $0417 / $20
Cs_6_f: EQU $0455 / $20
D_6_f: EQU $0497 / $20
Ds_6_f: EQU $04dd / $20
E_6_f: EQU $0527 / $20
F_6_f: EQU $0575 / $20
Fs_6_f: EQU $05c8 / $20
G_6_f: EQU $0620 / $20
Gs_6_f: EQU $067d / $20
A_6_f: EQU $06e0 / $20
As_6_f: EQU $0749 / $20
B_6_f: EQU $07b8 / $20
C_7_f: EQU $082d / $20
Cs_7_f: EQU $08a9 / $20
D_7_f: EQU $092d / $20
Ds_7_f: EQU $09b9 / $20
E_7_f: EQU $0a4d / $20
F_7_f: EQU $0aea / $20
Fs_7_f: EQU $0b90 / $20
G_7_f: EQU $0c40 / $20
Gs_7_f: EQU $0cfa / $20
A_7_f: EQU $0dc0 / $20
As_7_f: EQU $0e91 / $20
B_7_f: EQU $0f6f / $20
C_8_f: EQU $105a / $20
Cs_8_f: EQU $1153 / $20
D_8_f: EQU $125b / $20
Ds_8_f: EQU $1372 / $20
E_8_f: EQU $149a / $20
F_8_f: EQU $15d4 / $20
Fs_8_f: EQU $1720 / $20
G_8_f: EQU $1880 / $20
Gs_8_f: EQU $19f5 / $20
A_8_f: EQU $1b80 / $20
As_8_f: EQU $1d23 / $20
B_8_f: EQU $1ede / $20
Hemos añadido muchas constantes, pero recordad que EQU no se compila, por lo que no ocupa nada. Lo que hace el compilador es sustituir la etiqueta EQU por el valor que representa allí dónde aparezca.
Ahora que tenemos definidas las notas y sus frecuencias, vamos a definir las canciones, que en nuestro caso van a ser dos. En esta primera prueba no vais a reconocerlas, pero más adelante seguro que sí.
Para definir las canciones vamos a usar las etiquetas que acabamos de añadir en el archivo Const.asm. Creamos el archivo Var.asm (recordad que estamos dentro de la carpeta Sound) y vamos a agregar al mismo la primera canción.
Song_1:
dw G_2_f, G_2, G_2_f, G_2, G_2_f, G_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2
dw G_2_f, G_2, G_2_f, G_2, G_2_f, G_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2
dw D_3_f, D_3, D_3_f, D_3, D_3_f, D_3, Ds_3_f, Ds_3, As_2_f, As_2, Fs_2_f, Fs_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2
dw G_3_f, G_3, G_2_f, G_2, G_2_f, G_2, G_3_f, G_3, Fs_3_f, Fs_3, F_3_f, F_3, E_3_f, E_3, Ds_3_f, Ds_3, E_3_f, E_3
dw Gs_2_f, Gs_2, Cs_3_f, Cs_3, C_3_f, C_3, B_2_f, B_2, As_2_f, As_2, A_2_f, A_2, As_2_f, As_2
dw Ds_2_f, Ds_2, Fs_2_f, Fs_2, Ds_2_f, Ds_2, Fs_2_f, Fs_2, As_2_f, As_2, G_2_f, G_2, As_2_f, As_2, D_3_f, D_3
dw G_3_f, G_3, G_2_f, G_2, G_2_f, G_2, G_3_f, G_3, Fs_3_f, Fs_3, F_3_f, F_3, E_3_f, E_3, Ds_3_f, Ds_3, E_3_f, E_3
dw Gs_2_f, Gs_2, Cs_3_f, Cs_3, C_3_f, C_3, B_2_f, B_2, As_2_f, As_2, A_2_f, A_2, As_2_f, As_2
dw Ds_2_f, Ds_2, Fs_2_f, Fs_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2, A_2_f, A_2, G_2_f, G_2
dw G_2_f, G_2, G_2_f, G_2, G_2_f, G_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2
dw G_2_f, G_2, G_2_f, G_2, G_2_f, G_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2, Ds_2_f, Ds_2, As_2_f, As_2, G_2_f, G_2
Como podéis ver, la definición consta de parejas de valores de 16 bits (usando las etiquetas que hemos definido en Const.asm), la frecuencia seguida de la nota.
Vamos a añadir ahora la segunda de las canciones.
Song_2:
dw D_4_f, D_4, D_4_f, D_4, D_4_f, D_4, G_4_f, G_4, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_5_f, G_5, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_5_f, G_5, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, C_5_f, C_5, A_4_f, A_4
dw D_4_f, D_4, D_4_f, D_4, D_4_f, D_4, G_4_f, G_4, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_5_f, G_5, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_5_f, G_5, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, C_5_f, C_5, A_4_f, A_4
dw D_4_f, D_4, D_4_f, D_4, E_4_f, E_4, E_4_f, E_4, C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_4_f, G_4, G_4_f, G_4, A_4_f, A_4, B_4_f, B_4, A_4_f, A_4, E_4_f, E_4, Fs_4_f, Fs_4
dw D_4_f, D_4, D_4_f, D_4, E_4_f, E_4, E_4_f, E_4, C_5_f, C_5, C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_4_f, G_4, D_5_f, D_5, D_5_f, D_5, A_4_f, A_4
dw D_4_f, D_4, D_4_f, D_4, E_4_f, E_4, E_4_f, E_4, C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_4_f, G_4, G_4_f, G_4, A_4_f, A_4, B_4_f, B_4, A_4_f, A_4, E_4_f, E_4, Fs_4_f, Fs_4
dw D_5_f, D_5, D_5_f, D_5, G_5_f, G_5, F_5_f, F_5, Ds_5_f, Ds_5, D_5_f, D_5, C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_4_f, G_4, D_5_f, D_5
dw D_4_f, D_4, D_4_f, D_4, D_4_f, D_4, G_4_f, G_4, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_5_f, G_5, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, A_4_f, A_4, G_5_f, G_5, D_5_f, D_5
dw C_5_f, C_5, B_4_f, B_4, C_5_f, C_5, A_4_f, A_4
dw $0000
Observamos una última línea, DW $0000. Vamos a usar este valor para indicar que las canciones se han terminado y que, a partir de aquí, vuelva al principio de las mismas.
Por último, vamos a añadir la variable para guardar la siguiente nota.
ptrSound:
dw Song_2
Es el momento de implementar la rutina que vamos a usar para hacer sonar las canciones. Creamos el archivo Sound.asm e implementamos la rutina Play.
Play:
ld hl, (ptrSound)
ld e, (hl)
inc hl
ld d, (hl)
ld a, d
or e
jr nz, play_cont
Cargamos en HL la dirección de la siguiente nota, LD HL, (ptrSound), cargamos en E el byte inferior de la frecuencia, LD E, (HL), apuntamos HL a byte alto, INC HL, y lo cargamos en D, LD D, (HL). Cargamos el valor de D en A, LD A, D, comprobamos si el valor de la frecuencia es $0000 para saber si estamos en el final de la canción, OR E, y si no es así saltamos, JR NZ, play_cont.
play_reset:
ld hl, Song_1
ld (ptrSound), hl
ret
Si hemos llegado al final de las canciones, cargamos en HL la dirección de la canción uno, LD HL, Song_1, cargamos la primera nota en memoria, LD (ptrSound), HL, y salimos, RET.
play_cont:
inc hl
ld c, (hl)
inc hl
ld b, (hl)
inc hl
ld (ptrSound), hl
ld h, b
ld l, c
call BEEP
ret
Si no hemos llegado al final de la canción apuntamos HL al byte inferior de la nota, INC HL, lo cargamos en C, LD C, (HL), apuntamos HL al byte superior de la nota, INC HL, lo cargamos en B, LD B, (HL), apuntamos HL al byte inferior de la frecuencia de la siguiente nota, INC HL, y lo actualizamos en memoria, LD (ptrSound), HL, cargamos el byte superior de la nota en H, LD H, B, cargamos el byte inferior en L, LD L, C, y hacemos sonar la nota, CALL BEEP. Por último, salimos, RET.
Ya solo queda ver si todo esto funciona. Creamos el archivo Test1.asm y añadimos las líneas siguientes:
org $5dad
Loop:
call Play
jr Loop
include "Const.asm"
include "Sound.asm"
include "Var.asm"
end Loop
Ponemos la dirección de memoria dónde se cargará nuestro programa para que sea compatible con modelos de 16K, ORG $5DAD, llamamos a la rutina que se encarga de hacer sonar las canciones, CALL Play, y nos quedamos en un bucle infinito, JR Loop.
En la parte final del archivo, incluimos los ficheros necesarios e indicamos a PASMO (END Loop) que ejecute el programa.
Para ver si funciona, compilamos, cargamos en el emulador y escuchamos. Recordad que para compilar usamos la línea de comandos.
pasmo ––tapbas Test1.asm Test1.tap
Si todo ha ido bien, podréis distinguir dos canciones distintas, y los que tengan mejor oído incluso podrán reconocer de que canciones se trata.
Ritmo y compás
Efectivamente, las música no suena como tal, es necesario controlar el ritmo al que deben sonar las notas, y vamos a hacer uso de las interrupciones para ello; creamos el archivo Test2.asm y empezamos a implementar.
org $5dad
; -----------------------------------------------------------------------------
; Indicadores para la música.
;
; Bit 7 -> Reproducir sonido 1 = Sí / 0 = No
; -----------------------------------------------------------------------------
music:
db $00
Como siempre, empezamos con la posición de memoria donde se carga el programa, ORG $5DAD, y declaramos una etiqueta que va a servir para interactuar con las interrupciones, music.
Main:
ld hl, Song_2
ld (ptrSound), hl
di
ld a, $28
ld i, a
im 2
ei
Apuntamos HL a la segunda canción, LD HL, Song_2, y cargamos el valor en memoria, LD (ptrSound), HL. Desactivamos las interrupciones, DI, cargamos cuarenta en A, LD A, $28, lo cargamos en I, LD I, A, pasamos al modo dos de interrupciones, IM 2, y activamos la interrupciones, EI. En realidad, las interrupciones se activarían tras las siguiente instrucción.
Loop:
ld a, (music)
bit $07, a
jr z, Loop
and $7f
ld (music), a
call Play
jr Loop
Cargamos el valor de music en A, LD A, (music), evaluamos si el bit siete está a uno, BIT $07, A, y saltamos si no lo está, JR Z, Loop.
Si el bit está a uno lo ponemos a cero, AND $7F, lo cargamos en memoria, LD (music), A, emitimos el sonido, CALL Play, y seguimos en el bucle, JR Loop.
include "Const.asm"
include "Sound.asm"
include "Var.asm"
end Main
Por último, incluimos los ficheros y le indicamos a PASMO que incluya la llamada al programa en el cargador.
Ahora ya solo queda usar las interrupciones para controlar el ritmo. Creamos el archivo Int2.asm y empezamos.
org $7e5c
MUSIC: EQU $5dad
counter:db $00
Empezamos indicando donde se carga la rutina de interrupciones, ORG $7e5c, declarando una constante con la dirección de memoria donde está la etiqueta de los indicadores para la música y una etiqueta para controlar el número de interrupciones que tienen que pasar para que emitamos algún sonido.
El resto de la implementación la vamos a realizar entre MUSIC y counter.
Isr:
push af
push bc
push de
push hl
Preservamos el valor de los registros, PUSH AF, PUSH BC, PUSH DE y PUSH HL.
ld a, (counter)
inc a
ld (counter), a
cp $06
jr nz, isr_end
xor a
ld (counter), a
ld hl, MUSIC
set $07, (hl)
Cargamos en A el valor del contador, LD A, (counter), lo incrementamos, INC A, lo cargamos en memoria, LD (counter), A, evaluamos si ha llegado a seis, CP $06, y saltamos de no ser así, JR NZ, isr_end.
Si el contador ha llegado a seis lo ponemos a cero, XOR A, lo cargamos en memoria, LD (counter), A, apuntamos HL a los indicadores para la música, LD HL, MUSIC, y activamos el bit siete, SET $07, (HL).
isr_end:
pop hl
pop de
pop bc
pop af
ei
reti
Recuperamos el valor de los registros, POP HL, POP DE, POP BC y POP AF, activamos las interrupciones, EI, y salimos, RETI.
Volvemos a Test2.asm y justo encima de END Main incluimos el archivo Int2.asm.
include "Int2.asm"
El orden en el que se incluyen los archivos, en este caso concreto, es muy importante; primero vamos a compilar y ver como suena.
pasmo ––tapbas Test2.asm Test2.tap
Esta es la forma de onda que podemos ver en el emulador.
Ahora ya sí se pueden distinguir las canciones, aunque las dos van al mismo ritmo y no debería ser así.
Respecto al orden de los include, probad a poner el de Int2.asm por encima del de Var.asm. Compilad, cargad en el emulador (con el modelo de 16K) y a ver que pasa… ¡No funciona!
Comprobad el tamaño del programa Test2.tap, a mi me salen 9324 bytes. Volved a poner el include de Int.asm debajo del de Var.asm. Compilad y comprobad lo que ocupa ahora, a mi me salen 8520 bytes. ¿A qué se debe esta diferencia?
Al contrario que en el juego, no estamos compilando los ficheros por separado, estamos compilando como uno solo gracias a los include.
La memoria de los modelos de 16K va desde la posición $0000 hasta la $7FFF. Nosotros cargamos la rutina de interrupciones en la posición $7E5C, quedando cuatrocientos diecinueve bytes hasta la posición $7FFF pero ojo, hay que contar con la pila.
Cuando ponemos el include de Int2.asm el último, desde la posición $7E5C cargamos treinta dos bytes, que es lo que ocupa la rutina de las interrupciones que hemos implementado; quedamos muy lejos de ocupar los cuatrocientos diecinueve bytes que tenemos disponibles.
Si ponemos el include de Var.asm después del de Int.asm, tras los treinta y dos bytes de la rutina de interrupciones cargamos los ochocientos cuatro bytes de la definición de las canciones, sumado un total de ochocientos treinta y seis bytes ($0344). Si estos bytes se los sumamos a la dirección donde cargamos la rutina de interrupciones, $7E5C + $0344, nos da como resultado $81A0, muy por encima de la capacidad de los modelos de 16K.
Distintos ritmos
Para conseguir que las canciones, o incluso parte de ellas, vayan a distintos ritmos, vamos a añadir un nuevo valor de 16 bits: en el byte superior vamos a poner $FF, indicando a nuestro programa que se trata de un cambio de ritmo, mientras que en el byte inferior vamos a poner el ritmo, un valor de $00 a $0F.
Creamos el archivo Var3.asm y copiamos dentro todo el código del archivo Var.asm. Vamos a añadir dos cambios de ritmo. Localizamos la etiqueta Song_1 y justo debajo de ella añadimos:
dw $ff0c
Cuando el programa se encuentre con esto, lo va a interpretar como un cambio de ritmo ($FF) y que tienen que pasar doce interrupciones ($0C) entre cada nota.
Localizamos la etiqueta Song_2 y justo debajo de ella añadimos:
dw $ff06
En este caso tienen que pasar seis interrupciones ($06) entre cada nota, por lo que podemos deducir que la segunda canción va a sonar el doble de rápido que la primera.
Creamos el archivo Int3.asm y copiamos dentro todo el código del archivo Int.asm. Creamos el archivo Test3.asm y copiamos dentro todo el código de Test2.asm.
Empezamos modificando el archivo Test3.asm. En los include sustituimos Var.asm e Int.asm por Var3.asm e Int3.asm y añadimos una línea de comentario a la etiqueta music.
; Bit 0 a 3 -> Ritmo
El resto de las modificaciones las vamos a realizar justo antes de DI, añadiendo las siguientes líneas.
ld a, (hl)
and $0f
ld (music), a
En las líneas anteriores apuntábamos HL a Song_2, y ahora cargamos el valor al que apunta HL en A, LD A, (HL), nos quedamos con los bits donde ponemos el ritmo, AND $0F, y cargamos el valor en los indicadores para la música, LD (music), A.
Vamos al archivo Int3.asm y al final del mismo, justo por debajo de counter, vamos a añadir una nueva etiqueta para guardar el ritmo que lleva la canción.
times: db $00
Ahora, localizamos la etiqueta Isr y cinco líneas más abajo LD A, (counter); justo por encima de esta línea agregamos la siguiente:
isr_cont:
Cuatro líneas más abajo encontramos CP $06; modificamos esta línea dejándola como sigue:
cp (hl)
Otras cuatro líneas más abajo encontramos LD hl, MUSIC; justo por encima de esta línea agregamos la siguiente:
isr_set:
Ahora volvemos a la etiqueta Isr y después de los cuatro PUSH implementamos la parte en la que vamos a controlar los cambios de ritmo.
ld a, (MUSIC)
and $0f
ld hl, times
cp (hl)
jr z, isr_cont
ld (hl), a
xor a
ld (counter), a
jr isr_set
Cargamos en A el valor de los indicadores de la música, LD A, (MUSIC), nos quedamos con el ritmo, AND $0F, apuntamos HL a la variable donde guardamos el ritmo que lleva la canción, LD HL, times, lo comparamos con el ritmo que marcan los indicadores, CP (HL), y si son iguales saltamos pues no ha cambiado, JR Z, isr_cont.
Si ha cambiado el ritmo, ponemos el nuevo ritmo en memoria, LD (HL), A, ponemos A a cero, XOR A, y ponemos el contador a cero, LD (counter), A. Por último, saltamos, JR isr_set.
El aspecto que debe tener Int3.asm es el siguiente:
org $7e5c
MUSIC: EQU $5dad
Isr:
push af
push bc
push de
push hl
ld a, (MUSIC)
and $0f
ld hl, times
cp (hl)
jr z, isr_cont
ld (hl), a
xor a
ld (counter), a
jr isr_set
isr_cont:
ld a, (counter)
inc a
ld (counter), a
cp (hl)
jr nz, isr_end
xor a
ld (counter), a
isr_set:
ld hl, MUSIC
set $07, (hl)
isr_end:
pop hl
pop de
pop bc
pop af
ei
reti
counter: db $00
times: db $00
Ya solo falta modificar la rutina Play para que tenga en cuenta los cambios de ritmo; vamos al archivo Sound.asm y tras la quinta línea, OR E, vamos a añadir las siguientes:
jr z, play_reset
cp $ff
Con OR E comprobamos si se ha llegado al fin de las canciones, en cuyo caso saltamos, JR Z, play_reset. Si no hemos llegado al final de las canciones, comprobamos si estamos ante un cambio de ritmo, CP $FF.
La siguiente línea ya la teníamos antes, JR NZ, play_cont, y ahora lo que hace es saltar si no hay un cambio de ritmo.
Seguimos añadiendo líneas justo debajo de JR NZ, play_cont.
ld a, e
ld (music), a
inc hl
ld (ptrSound), hl
ret
Si no hemos saltado es porque hay un cambio de ritmo. Cargamos el nuevo ritmo en A, LD A, E, lo cargamos en los indicadores para la música, LD (music), A, apuntamos HL a la próxima nota (a la frecuencia), INC HL, actualizamos el valor del puntero a la próxima nota, LD (ptrSound), HL, y salimos, RET. El resto de la rutina se queda como está.
Es el momento de ver como suena, compilamos, cargamos en el emulador y escuchamos los resultados.
pasmo ––tapbas Test3.asm Test3.tap
Si todo va bien, las dos canciones se reproducen a distinto ritmo, lo cual podéis apreciar escuchando los resultados o viendo la forma de onda, donde veréis claramente que van a distinta velocidad.
Ensamblador ZX Spectrum, conclusión
En este capítulo hemos hecho pruebas de como implementar la música.
En el próximo capítulo vamos a utilizar lo aprendido para integrar la música en nuestro juego.
Podéis descargar todo el código que hemos implementado desde aquí.
Enlaces de interés
- Notepad++
- Visual Studio Code
- Sublime Text
- ZEsarUX
- PASMO
- Git
- Curso de ensamblador Z80 de Compiler Software
- Referencia Z80
- Ensamblador Z80 en Telegram
- Tutorial completo en formato PDF y EPUB
- Proyecto en itch.io
- Archivo .dsk con los juegos de los tutoriales
- Personalización y depuración con ZEsarUX
Ensamblador para ZX Spectrum Batalla espacial 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.