Espamática
ZX SpectrumRetroZ80 Assembly

ZX Spectrum Assembly, Space Battle – 0x0D Music

In this chapter of ZX Spectrum Assembly, it is time to integrate everything we saw in the previous chapter into our game, there will be some small variations, but it is practically the same. Also, it is time to comment all the code, there are parts that are not commented yet and if we leave it like this, it is possible that in time it will be difficult to know what we are doing there and, more importantly, why?

Create the folder Step13 and copy from the folder Step12 the files loader.tap, const.asm, ctrl.asm, game.asm, graph.asm, int.asm, main.asm, make or make.bat, print.asm, var.asm and the folder sound.

Translation by Felipe Monge Corbalán

Table of contents

Constants

We start by declaring the necessary constants in the const.asm file, starting with the ROM routine we are going to use. Locate the UDG tag and add the following just below it:

ASM
; -------------------------------------------------------------------
; ROM beeper routine.
;
; Input: HL -> Note.
;        DE -> Duration.
;
; Alters the value of the AF, BC, DE, HL and IX registers.
; -------------------------------------------------------------------
BEEP: EQU $03b5

It is very important to read the comments, as we can see that this ROM routine changes the value of almost all the registers. We did not take this into account in the previous chapter, but we will in this chapter.

At the end of the const.asm file we will add the note constants and frequencies we saw in the previous chapter.

Variables

The next step is to add the necessary variables: the pointer to the next note and the songs. Open var.asm and add the following lines at the end:

ASM
; -------------------------------------------------------------------
; Necessary data for music.
; -------------------------------------------------------------------

; -------------------------------------------------------------------
; Next note
; -------------------------------------------------------------------
ptrSound:
dw $0000

; -------------------------------------------------------------------
; Songs
; -----------------------------------------------------------------
Song_1:
dw $ff0c
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
dw G_2_f,G_2,   Ds_2_f,Ds_2,  As_2_f,As_2,  G_2_f, G_2,   G_2_f,G_2
dw 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
dw Ds_2_f,Ds_2, As_2_f,As_2,  G_2_f,G_2,    D_3_f,D_3,    D_3_f,D_3
dw 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
dw 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
dw F_3_f,F_3,   E_3_f,E_3,    Ds_3_f,Ds_3,  E_3_f,E_3,    Gs_2_f,Gs_2
dw 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
dw As_2_f,As_2, Ds_2_f,Ds_2,  Fs_2_f,Fs_2,  Ds_2_f,Ds_2,  Fs_2_f,Fs_2 
dw 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
dw F_3_f,F_3,   E_3_f,E_3,    Ds_3_f,Ds_3,  E_3_f,E_3,    Gs_2_f,Gs_2
dw 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
dw As_2_f,As_2, Ds_2_f,Ds_2,  Fs_2_f,Fs_2,  Ds_2_f,Ds_2,  As_2_f,As_2
dw 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
dw G_2_f,G_2,   Ds_2_f,Ds_2,  As_2_f,As_2,  G_2_f,G_2,    G_2_f,G_2
dw 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
dw Ds_2_f,Ds_2, As_2_f,As_2,  G_2_f,G_2

Song_2:
dw $ff05
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
dw 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
dw B_4_f,B_4,   A_4_f,A_4,    E_4_f,E_4,    Fs_4_f,Fs_4,  D_4_f,D_4
dw 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
dw 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
dw A_4_f,A_4,   D_4_f,D_4,    D_4_f,D_4,    E_4_f,E_4,    E_4_f,E_4
dw 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
dw 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
dw 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
dw 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

As we saw in the previous chapter, we need a flags variable for the music. Let’s go to main.asm and the game flags. Just below that we add the flags for the music.

ASM
; -------------------------------------------------------------------
; Music indicators
;
; Bit 0 to 3 -> Rhythm
; Bit 7      -> sounds 0 = No, 1 = Yes
; -------------------------------------------------------------------
music:
db $00

Remember that these labels must be set to zero at the start, otherwise everything may stop working.

Reproduction

We continue with the routine that will be responsible for playing the sound; we go to game.asm.

It is necessary to specify the song to be played and the rhythm. Since we have two songs, we start with the first song on even levels and the second song on odd levels. We locate ChangeLevel and see that it is the eighth and ninth lines:

ASM
inc  a
cp   $1f

Right between these two lines we will implement the change of song depending on the level:

ASM
ld   hl, Song_1
bit  $00, a
jr   z, changeLevel_cont
ld   hl, Song_2
changeLevel_cont:
ld   (ptrSound), hl

ex   af, af'
ld   a, (hl)
ld   (music), a
ex   af, af'

We point HL to the first song, LD HL, Song_1, look at the zero bit of A to see if the next level is odd or even, BIT $00, A, and skip if it is even. If it is odd, we point HL to the next song, LD HL, Song_2.

We update the pointer to the note (to the beat), LD (ptrSound), HL, keep the value of AF since A contains the next level, EX AF, AF’, load the beat in A, LD A, (HL), update the indicators, LD (music), A, and restore the value of AF, EX AF, AF’.

The final aspect of the routine is as follows:

ASM
; -------------------------------------------------------------------
; Change level.
;
; Alters the value of the AF, BC, DE and HL registers.
; -------------------------------------------------------------------
ChangeLevel:
ld   a, $06                ; A = yellow colour
ld   (enemiesColor), a     ; Update in memory

ld   a, (levelCounter + 1) ; A = current level in BCD
inc  a                     ; Increases level
daa                        ; Decimal adjust
ld   b, a                  ; B = A
ld   a, (levelCounter)     ; A = current level
inc  a                     ; A = next level

ld   hl, Song_1            ; HL = song 1
bit  $00, a                ; Even level?
jr   z, changeLevel_cont   ; Yes, jump
ld   hl, Song_2            ; No, HL = song 2

changeLevel_cont:
ld   (ptrSound), hl        ; Update next note
ex   af, af'               ; Preserves AF (A = next level)
ld   a, (hl)               ; A = lower byte note (beat)
ld   (music), a            ; Updates indicators
ex   af, af'               ; Retrieves AF
cp   $1f                   ; Level 31?
jr   c, changeLevel_end    ; No, skip
ld   a, $01                ; Yes, set it to 1
ld   b, a                  ; B = A

changeLevel_end:
ld   (levelCounter), a     ; Update level in memory 
ld   a, b                  ; A = B (BCD level)
ld   (levelCounter + 1), a ; Update in memory
call LoadUdgsEnemies       ; Load enemy graphics

ld   a, $20                ; A = total number of enemies, 20 BCD
ld   (enemiesCounter), a   ; Updates in memory

ld   hl, enemiesConfigIni  ; HL = enemies initial config
ld   de, enemiesConfig     ; HL = enemies config
ld   bc, enemiesConfigEnd-enemiesConfigIni ; BC = config length
ldir                       ; Load initial config in config

ld   hl, shipPos           ; HL = ship position
ld   (hl), SHIP_INI        ; Update in memory

ret

Now let’s implement the routine that plays the songs; we’ll do this before the RefreshEnemiesFire routine.

ASM
Play:
ld   hl, (ptrSound)
ld   e, (hl)
inc  hl
ld   d, (hl)
ld   a, d
or   e
jr   z, play_reset

We load the note address into HL, LD HL, (ptrSound), the lower byte of the frequency into E, LD E, (HL), point HL to the upper byte, INC HL, load it into D, LD D, (HL), load it into A, LD A, D, and check whether the end of the song has been reached, OR E, in which case we jump, JR Z, play_reset.

ASM
cp   $ff
jr   nz, play_cont
ld   a, e
ld   (music), a
inc  hl
ld   (ptrSound), hl
ret

If we have not skipped, we check if the note is actually a beat change, CP $FF, and skip if it is not, JR NZ, play_cont.

If it is a beat change, we load it into A, LD A, E, update the music pointer, LD (music), A, point HL to the next note (frequency), INC HL, update the pointer, LD (ptrSound), HL, and exit, RET.

ASM
play_reset:
ld   hl, Song_1
ld   (ptrSound), hl
ret

If it is the end of the songs, we point HL to the first song, LD HL, Song_1, update pointer, LD (ptrSound), HL, and exit, RET.

ASM
play_cont:
inc  hl
ld   c, (hl)
inc  hl
ld   b, (hl)
inc  hl
ld   (ptrSound), hl
ld   h, b
ld   l, c

If we have not reached the end of the songs and there has been no tempo change, we point HL to the lower byte of the note, INC HL, load it into C, LD C, (HL), point HL to the upper byte, INC HL, load it into B, LD B, (HL), point HL to the next note, INC HL, update pointer, LD (ptrSound), HL, load the upper byte of the note into H, LD H, B, and the lower byte into L, LD L, C.

At the beginning of the chapter we declared the BEEP tag with the value of the memory address where the ROM BEEPER routine is located, which receives the note in HL and the frequency in DE. We can now call it.

This is the main difference from the implementation we did in the previous chapter. We are going to add some sort of special effect, so we are going to implement the ROM call in our own routine.

ASM
Play_beep:
push af
push bc
push de
push hl
call BEEP
pop  hl
pop  de
pop  bc
pop  af

ret

We hold the register values, PUSH, call the ROM routine to play the note, CALL BEEP, retrieve the register value, POP, and exit, RET.

We can now call Play to play music during the game, and Play_beep to play single notes, such as sound effects.

It is not possible to change the order in which the routines are implemented, please note that Play comes from Play_beep, if we change the order the result may not be what we want.

The final aspect of the routine is as follows:

ASM
; -------------------------------------------------------------------
; He plays the songs.
;
; Alters the value of the AF, BC, DE and HL registers.
; -------------------------------------------------------------------
Play:
ld   hl, (ptrSound)        ; HL = current note address
ld   e, (hl)               ; E = lower byte frequency
inc  hl                    ; HL = upper byte
ld   d, (hl)               ; D = upper byte
ld   a, d                  ; A = D
or   e                     ; End songs?
jr   z, play_reset         ; Yes, jump
cp   $ff                   ; Does it change tempo?
jr   nz, play_cont         ; No, jump
ld   a, e                  ; A = new rhythm
ld   (music), a            ; Update in memory
inc  hl                    ; HL = next note
ld   (ptrSound), hl        ; Update pointer
ret

play_reset:
ld   hl, Song_1            ; HL = first song
ld   (ptrSound), hl        ; Update pointer
ret

play_cont:
inc  hl                    ; HL = lower byte note
ld   c, (hl)               ; C = lower byte note
inc  hl                    ; HL = upper byte
ld   b, (hl)               ; B = upper byte
inc  hl                    ; HL = frequency next note
ld   (ptrSound), hl        ; Update pointer
ld   h, b 
ld   l, c                  ; HL = note

; -------------------------------------------------------------------
; He sounds a note.
;
; Input: HL -> Note
;        DE -> Frequency
; -------------------------------------------------------------------
Play_beep:
push af
push bc
push de
push hl                    ; Preserves records
call BEEP                  ; Call ROM routine
pop  hl
pop  de
pop  bc
pop  af                    ; Retrieves records

ret

We need to put the call to Play inside the main loop. We go back to main.asm, find Main_loop and within it the line CALL CheckCrashShip. Below this line we add:

ASM
ld   hl, music
bit  $07, (hl)
jr   z, main_loopCont
res  07, (hl)
call Play

main_loopCont:

We point HL to the music flags, LD HL, music, check if bit seven is set, BIT $07, (HL), and skip if not, JR Z, main_loopCont.

We deactivate it if it is active, RES $07, (HL), and make the next note sound, CALL Play.

Finally, we add the tag to jump to if bit seven is not active, main_loopCont.

The appearance of main.asm after commenting is as follows:

ASM
org  $5dad

; -------------------------------------------------------------------
; Indicators
;
; Bit 0 -> ship must be moved 0 = No, 1 = Yes
; Bit 1 -> shot is active 0 = No, 1 = Yes
; Bit 2 -> enemies must be moved 0 = No, 1 = Yes
; Bit 3 -> change address enemies 0 = No, 1 = Yes
; Bit 4 -> move enemy shot 0 = No, 1 = Yes
; -------------------------------------------------------------------
flags:
db $00

; -------------------------------------------------------------------
; Music indicators
;
; Bit 0 to 3 -> Rhythm
; Bit 7      -> sounds 0 = No, 1 = Yes
; -------------------------------------------------------------------
music:
db $00

Main:
ld   a, $02
call OPENCHAN              ; Opens channel 2, top screen

ld   hl, udgsCommon        ; HL = UDG address
ld   (UDG), hl             ; Update UDG address

ld   hl, ATTR_P            ; HL = address permanent attributes
ld   (hl), $07             ; White ink and black background
call CLS                   ; Clear screen

xor  a                     ; A = 0
out  ($fe), a              ; Border = black
ld   a, (BORDCR)           ; A = BORDCR
and  $c0                   ; A = brightness and flash
or   $05                   ; A += ink 5 and background 0
ld   (BORDCR), a           ; Update BORDCR

di                         ; Disables interruptions
ld   a, $28                ; A = 40
ld   i, a                  ; I = A
im   2                     ; Mode 2 interruptions
ei                         ; Activates interruptions

ld   a, (flags)            ; A = flags
Main_start:
ld   hl, enemiesCounter    ; HL = enemiesCounter
ld   de, enemiesCounter+$01 ; DE = level counter
ld   (hl), $00             ; Enemies counter = 0
ld   bc, $08               ; BC number of bytes to clear
ldir                       ; Clean bytes
ld   a, $05 
ld   (livesCounter), a     ; Lives = 5

call ResetEnemiesFire      ; Initialises enemy shot
call ChangeLevel           ; Change level
call PrintFirstScreen      ; Paint menu screen and wait
call PrintFrame            ; Paint frame
call PrintInfoGame         ; Paint game info titles
call PrintShip             ; Paint ship
call PrintInfoValue        ; Paint info. value

call LoadUdgsEnemies       ; Load enemies
call PrintEnemies          ; Paints them
; Delay
call Sleep                 ; Delay before starting the level

; Main loop
Main_loop:
call CheckCtrl             ; Check controls
call MoveFire              ; Move fire

push de                    ; Preserves DE
call CheckCrashFire        ; Evaluates enemy collision/shootback
pop  de                    ; Retrieve DE

ld   a, (enemiesCounter)   ; A = number of active enemies
or   a                     ; Is it 0?
jr   z, Main_restart       ; Yes, jump

call MoveShip              ; Move ship
call ChangeEnemies         ; Change enemies address if applicable
call MoveEnemies           ; Move enemies
call MoveEnemiesFire       ; Move enemy fire
call CheckCrashShip        ; Evaluates ship/enemies/shot collision

ld   hl, music             ; HL = music indicators
bit  $07, (hl)             ; Sound note?
jr   z, main_loopCont      ; No, jump
res  $07, (hl)             ; Disables bit seven of music
call Play                  ; Play note

main_loopCont:
ld   a, (livesCounter)     ; A = lives
or   a                     ; Is it 0?
jr   z, GameOver           ; Yes, GAME OVER!

jr   Main_loop             ; Main loop

Main_restart:
ld   a, (levelCounter)     ; A = level
cp   $1e                   ; Is it 31 (we have 30)
jr   z, Win                ; Yes, VICTORY!

call FadeScreen            ; Fade screen
call ChangeLevel           ; Change level
call PrintFrame            ; Paint frame
call PrintInfoGame         ; Paint game info titles
call PrintShip             ; Paint ship
call PrintInfoValue        ; Print info. value
call PrintEnemies          ; Paint Enemies
call ResetEnemiesFire      ; Resets enemy shots
; Delay
call Sleep                 ; Delay
jr   Main_loop             ; Main loop

; GAME OVER!
GameOver:
xor  a                     ; A = 0
call PrintEndScreen        ; Print end screen and wait
jp   Main_start            ; Main menu

; VICTORY!
Win:
ld   a, $01                ; A = 1
call PrintEndScreen        ; Paint end screen and wait
jp   Main_start            ; Main menu

include "const.asm"
include "ctrl.asm"
include "game.asm"
include "graph.asm"
include "print.asm"
include "var.asm"

end  Main

Music control by interruptions

The interrupt routine is where we indicate when a new note needs to be played. We go to int.asm and the first thing we do is add two constants before T1: EQU $C8:

ASM
FLAGS: EQU $5dad
MUSIC: EQU $5dae

These tags refer to the memory locations where we have defined the flags in main.asm.

After the four PUSH of the Isr label, we find the line LD HL, $5DAD, which we will modify as follows:

ASM
ld   hl, FLAGS

At the end of the file we will add the variables to store the rhythm of the song, and the counter to know if the bit to play a note should be activated.

ASM
countTempo: db $00
tempo:      db $00

Locate Isr_T1, modify the fifth line, JR NZ, Isr_end and leave it as follows:

ASM
jr   nz, Isr_sound

We locate Isr_end and implement the music control above it.

ASM
Isr_sound:
ld   a, (MUSIC)
and  $0f
ld   hl, tempo
cp   (hl)
jr   z, Isr_soundCont
ld   (hl), a
jr   Isr_soundEnd

We load into A the music flags, LD A, (MUSIC), keep the beat, AND $0F, point HL to the current tempo, LD HL, and compare it with the beat in the music flags, CP (HL). If the two values are the same, there is no tempo change and we jump, JR Z, Isr_soundCount. If they are different, we update, LD (HL), A, and jump, JR Isr_soundEnd.

ASM
Isr_soundCont:
ld   a, (countTempo)
inc  a
ld   (countTempo), a
cp   (hl)
jr   nz, Isr_end

We load the beat counter into A, LD A, (countTempo), increment it, INC A, update in memory, LD (countTempo), A, compare them, CP (HL), if they are different we don’t have to play the note and jump, JR NZ, Isr_end.

ASM
Isr_soundEnd:
xor  a
ld   (countTempo), a
ld   hl, MUSIC
set  07, (hl)

If we have not jumped, we must sound the note. Set A to zero, XOR A, update the beat counter, LD (countTempo), A, point HL to the indicators, LD HL, MUSIC, and set bit seven to indicate that a note should be played, SET $07, (HL).

The final appearance of int.asm, once commented, is as follows:

ASM
org  $7e5c

FLAGS: EQU $5dad           ; General indicators
MUSIC: EQU $5dae           ; Indicators music
T1:    EQU $c8             ; Interruptions to activate change
                           ; of enemy leadership

Isr:
push hl
push de
push bc
push af                    ; Preserves records

ld   hl, FLAGS             ; HL = indicators
set  $00, (hl)             ; Activate bit 0, move ship

ld   a, (countEnemy)       ; A = counter move enemies
inc  a                     ; A = A+1
ld   (countEnemy), a       ; Update in memory
sub  $03                   ; A = A+3
jr   nz, Isr_T1            ; Non-zero, jumps
ld   (countEnemy), a       ; Counter = zero
set  $02, (hl)             ; Activate bit 2, move enemies 
set  $04, (hl)             ; Activate bit 4, move enemy shot

; Change of direction of enemies
Isr_T1:
ld   a, (countT1)          ; A = counter change address
inc  a                     ; A = A+1
ld   (countT1), a          ; Update in memory
sub  T1                    ; A = A-T1 (interruptions to be passed)
jr   nz, Isr_sound         ; Non-zero, skip
ld   (countT1), a          ; Counter = zero
set  $03, (hl)             ; Activates bit 3 flags 
                           ; change address enemies

; Sound
Isr_sound:
ld   a, (MUSIC)            ; A = music indicators
and  $0f                   ; A = tempo
ld   hl, tempo             ; HL = current tempo
cp   (hl)                  ; Compares tempo
jr   z, Isr_soundCont      ; Equals, jumps
ld   (hl), a               ; Different, current tempo
jr   Isr_soundEnd          ; Jump to sound note

Isr_soundCont:
ld   a, (countTempo)       ; A = countTempo
inc  a                     ; A = A+1 
ld   (countTempo), a       ; Update in memory
cp   (hl)                  ; Compare with current rate
jr   nz, Isr_end           ; Distinct, jumps

Isr_soundEnd:
xor  a                     ; A = 0
ld   (countTempo), a       ; Tempo counter = 0
ld   hl, MUSIC             ; HL = music indicators
set  $07, (hl)             ; Activate bit 7, sound

Isr_end:
pop  af
pop  bc
pop  de
pop  hl                    ; Retrieves records
ei                         ; Activates interruptions
reti                       ; Exits

countT1:    db $00         ; Counter change direction enemies
countEnemy: db $00         ; Enemy movement counter
countTempo: db $00         ; Counter to sound notes
tempo:      db $00         ; Current tempo

When we compile and load it into the emulator, there should be music playing during the game, and on every level, whether odd or even, one song or another should start playing.

If the difficulty is too high, comment out the line CALL CheckCrashShip in the main loop to avoid being killed.

Sound effects

Apart from the music, we will implement three different types of sound effects:

  • Enemy movement.
  • Explosion of the ship.
  • Shot.

We implement these sound effects in separate routines within game.asm. Since we’ve already seen how to make each note sound, let’s look at the final code of the routines without going into detail.

Locate the RefreshEnemiesFire routine and add the following lines before it:

ASM
; -------------------------------------------------------------------
; Emits the sound of enemy movement.
;
; Alters the value of the HL and DE registers.
; -------------------------------------------------------------------
PlayEnemiesMove:
ld   hl, $0a               ; HL = note
ld   de, $00               ; DE = frequency
call Play_beep             ; Play the sound

ld   hl, $14               ; HL = note
ld   de, $20               ; DE = frequency
call Play_beep             ; Play the sound

ld   hl, $0a               ; HL = note
ld   de, $10               ; DE = frequency
call Play_beep             ; Play the sound

ld   hl, $30               ; HL = note
ld   de, $1e               ; DE = frequency
jr   Play_beep             ; Play the sound and exit

; -------------------------------------------------------------------
; Emits the sound of the ship's explosion.
;
; Alters the value of the HL and DE registers.
; -------------------------------------------------------------------
PlayExplosion:
ld   hl, $27a0             ; HL = note
ld   de, $2b/$20           ; DE = frequency
call Play_beep             ; Play the sound

ld   hl, $13f4             ; HL = note
ld   de, $37/$20           ; DE = frequency
call Play_beep             ; Play the sound

ld   hl, $14b9             ; HL = note
ld   de, $52/$20           ; DE = frequency
call Play_beep             ; Play the sound

ld   hl, $1a2c             ; HL = note
ld   de, $41/$20           ; DE = frequency
jr   Play_beep             ; Play the sound and exit

; -------------------------------------------------------------------
; Emits the sound of the ship's gunfire
;
; Alters the value of the HL and DE registers.
; -------------------------------------------------------------------
PlayFire:
ld   hl, $64               ; HL = note
ld   de, $01               ; DE = frequency
jr   Play_beep             ; Play sound and exit

As we can see, in the three routines we load notes in HL, frequencies in DE, and we emit sounds, CALL Play_beep, except in the last note of each effect, in which case we use JR to exit with the RET of Play_beep.

All that remains now is to call the implemented routines. We locate MoveEnemiesFire and its last line, which is JP RefreshEnemiesFire. Just above this line we add the call to the sound we want to make when the enemies move, more specifically when they shots.

ASM
call PlayEnemiesMove       ; Play EnemiesMove

The next call we add is to the sound that is made when the gun is fired. We locate MoveFire and after the sixth line, SET $01, (HL), we add the following lines:

ASM
push hl                    ; Preserves HL
call PlayFire              ; Play shot sound
pop  hl                    ; Retrieves HL

In the case of the explosion sound, we will not call it from game.asm, even though it seems inconsistent.

If we locate the checkCrashShip_endLoop tag, which is inside the CheckCrashShip routine, and we look at the previous line JP PrintExplosion, we can deduce that when the ship is hit we paint the explosion, and if we go to PrintExplosion we see that it paints the explosion and jumps to paint the ship, JP PrintShip and goes out that way.

So, in order not to change the current behaviour, and even though it is not coherent, the call to the sound emission will be made from PrintExplosion. We go to the print.asm file, find the PrintExplosion routine and the last line, which is JP PrintShip. On top of this line we add the following line:

ASM
call PlayExplosion         ; Emits explosion sound

Compile, load into the emulator and see the results.

ZX Spectrum Assembly, Space Battle

We now have the music and all the sound effects for our game.

In the next chapter of ZX Spectrum Assembly, we will allow you to choose between five difficulty levels, mute the music during the game and include the loading screen.

Download the source code from here.

ZX Spectrum Assembly, Space Battle by Juan Antonio Rubio García.
Translation by Felipe Monge Corbalán.
This work is licensed to Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).
Any comments are always welcome.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies

Descubre más desde Espamática

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo