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.
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
- Ensamblador ZX Spectrum Pong
- Referencia Z80
- Ensamblador Z80 en Telegram
- Tutorial completo en formato PDF y EPUB
- Proyecto en itch.io
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.