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
- Variables
- Reproduction
- Music control by interruptions
- Sound effects
- ZX Spectrum Assembly, Space Battle
- Useful links
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:
; -------------------------------------------------------------------
; 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:
; -------------------------------------------------------------------
; 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.
; -------------------------------------------------------------------
; 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:
inc a
cp $1f
Right between these two lines we will implement the change of song depending on the level:
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:
; -------------------------------------------------------------------
; 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.
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.
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.
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.
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.
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:
; -------------------------------------------------------------------
; 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:
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:
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:
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:
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.
countTempo: db $00
tempo: db $00
Locate Isr_T1, modify the fifth line, JR NZ, Isr_end and leave it as follows:
jr nz, Isr_sound
We locate Isr_end and implement the music control above it.
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.
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.
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:
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:
; -------------------------------------------------------------------
; 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.
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:
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:
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.
Useful links
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.