Espamática
ZX SpectrumRetroZ80 Assembly

ZX Spectrum Assembly, Space Battle – 0x0C Sound

In this chapter of ZX Spectrum Assembly, we will prepare the in-game music and sound effects.

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

Testing, testing

Before we implement the sound, we are going to do a series of tests to see how we do it.

Inside the Step12 folder we will create the Sound folder, and inside it we will add the code files of the tests we are going to carry out.

Inside the Sound folder, we will create the const.asm file to add the constants we will need, such as the memory address of the BEEPER routine of the ROM that will play the notes we send.

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

We can see from the comments that this routine receives the note in the HL register and the duration (frequency) in the DE register.

Next, we will add the notes and frequencies to the file.

; -------------------------------------------------------------------
; Notes to be uploaded to 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

The lower case «S» after some notes indicates that it is a sustained note, the number is the scale. If the number is smaller, the note is lower, if it is larger, the note is higher.

We also add the frequencies, using the same notation, but adding _f to distinguish them from the notes. These frequencies make the notes sound for one second, but if we divide them by two, they would sound for half a second. Be careful, while a sound is being emitted our programme stops, so we divide the frequencies by thirty-two.

; -------------------------------------------------------------------
; Frequencies to be loaded in DE, 1 second ( / 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

We’ve added a lot of constants, but remember that EQU is not compiled, so it doesn’t take up any space. When compiled, the EQU tag will be replaced with the value it represents wherever it appears.

Now that we have defined the notes and their frequencies, we will define the songs, which in our case will be two. You won’t see them now, but you will see them later.

To define the songs we will use the tags added to const.asm. We create the var.asm file (in the Sound folder) and add the first song.

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
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

As you can see, the definition consists of 16-bit value pairs (using the labels defined in const.asm), frequency and note.

Now let us add the second of the songs.

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
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

The last line is DW $0000. We use this value to indicate that the songs are finished, and from here we go back to the beginning of the songs.

Finally, we add the variable to store the next note.

ptrSound:
dw Song_2

Now it’s time to implement the routine we’ll use to play the songs. We will create sound.asm and implement Play.

Play:
ld   hl, (ptrSound)
ld   e, (hl)
inc  hl
ld   d, (hl)
ld   a, d
or   e
jr   nz, play_cont

We load the direction of the sound into HL, LD HL, (ptrSound), the lower byte of the frequency into E, LD E, (HL), we set HL to the top, INC HL, and load it into D, LD D, (HL). We load the value of D into A, LD A, D, check if the frequency value is $0000 to know if we are at the end of the song, OR E, and if not we jump, JR NZ, play_cont.

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

When we have reached the end of the songs, we load the address of song one in HL, LD HL, Song_1, update in memory, 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

call BEEP

ret

If we haven’t reached the end of the song, 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 of the note, INC HL, load it into B, LD B, (HL), point HL to the lower byte of the frequency of the next note, INC HL, and update it in memory, LD (ptrSound), HL. We load the high byte of the note into H, LD H, B, load the low byte into L, LD L, C, sound the note, CALL BEEP, and exit, RET.

Let’s see if it works. We create the file test1.asm and add the following lines:

org  $5dad

Loop:
call Play

jr   Loop

include "const.asm"
include "sound.asm"
include "var.asm"

end  Loop

We set the memory address where the program will be loaded to be compatible with 16K models, ORG $5DAD, we call the routine responsible for playing the songs, CALL Play, and we stay in an infinite loop, JR Loop.

In the last part of the archive we include the necessary files.

To see if it works, we compile, load and listen. To compile we use the command line.

pasmo --tapbas test1.asm test1.tap

If all goes well, you will be able to distinguish two different songs, and those with better hearing will even be able to tell which songs they are, although it is very difficult.

Rhythm and beat

In fact, the music does not sound good. It is necessary to control the rhythm in which the notes sound, and we will use the interruptions to do this. We create the file test2.asm and start the implementation.

org  $5dad

; -------------------------------------------------------------------
; Indicators for music.
;
; Bit 7 -> Play sound 1 = Yes / 0 = No
; -------------------------------------------------------------------
music:
db $00

We start with the address where we load the program, ORG $5DAD, and declare a label that will be used to interact with the interrupts, music.

Main:
ld   hl, Song_2
ld   (ptrSound), hl

di
ld   a, $28
ld   i, a
im   2
ei

We point HL to the second song, LD HL, Song_2, load the value into memory, LD (ptrSound), HL, disable interrupts, DI, load forty into A, LD A, $28, then into I, LD I, A, go to interrupt mode two, IM 2, and enable them, EI. They don’t actually become active until the next instruction.

Loop:
ld   a, (music)
bit  $07, a
jr   z, Loop
and  $7f
ld   (music), a

call Play

jr   Loop

We load the value of music into A, LD A, (music), check if bit seven is set to one, BIT $07, A, and jump, JR Z, Loop, if not.

If the bit is one, we set it to zero, AND $7F, load to memory, LD (music), A, play sound, CALL Play, and continue looping, JR Loop.

include "const.asm"
include "sound.asm"
include "var.asm"

end  Main

Finally, we include the files and tell PASMO to include the program call in the loader.

The only thing left to do is to use the interrupts and control the rhythm. We create the file int2.asm and start.

org  $7e5c

MUSIC: EQU $5dad

counter:
db $00

We indicate where to load the interrupt routine, ORG $7E5C, declare a constant with the address where the labels of the indicators for the music are, and a counter for the number of interrupts to pass before we emit a sound.

The rest of the implementation is done between MUSIC and counter.

Isr:
push af
push bc
push de
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)

isr_end:
pop  hl
pop  de
pop  bc
pop  af

ei
reti

We keep the value of the PUSH registers. We load the value of the counter into A, LD A, (counter), increment it by one, INC A, load it into memory, LD (counter), A, evaluate if it has reached six, CP $06, and jump if not, JR NZ, isr_end.

If the counter has reached six, we set it to zero, XOR A, load into memory, LD (counter), A, point HL to the music indicator, LD HL, MUSIC, and set bit seven, SET $07, (HL).

We retrieve the registers, POP, activate the interrupts, EI, and exit, RETI.

We go back to test2.asm and just above END Main we insert int2.asm.

include "int2.asm"

The order in which the files are included is very important in this case. Let’s compile and see how it sounds.

pasmo --tapbas test2.asm test2.tap

Now we can tell the songs apart, even though they have the same rhythm and should not.

This is the waveform we see in the emulator (Menu\ Audio\ Waveform).

ZX Spectrum Assembly, Space Battle
ZX Spectrum Assembly, Space Battle

As for the order of the include, try putting int2.asm before var.asm. Compile, load with the 16K model and see what happens… It doesn’t work!

Check the size of test2.tap, it should be about 9324 bytes. Leave the include as they were, compile and check the size, it should be about 8520 bytes. What is the reason for this difference?

Unlike the game, we do not compile the files separately, we only compile one file thanks to the include.

The memory of the 16K models goes from position $0000 to $7FFF. We load the interrupt routine at $7E5C, and we have 419 bytes to go to $7FFF, but be careful, we have to count with the stack.

If we put the int2.asm include at the end, we load thirty-two bytes from $7E5C, which takes up the interrupt routine and is well short of the four hundred and nineteen bytes we have available.

If we put the var.asm include after the int.asm include, after the thirty-two bytes of the interrupt routine, the bytes of the song definition are loaded, eight hundred and four bytes, for a total of eight hundred and thirty-six bytes ($0344). If we add these bytes to the address where we load the interrupts, $7E5C+$0344, we get $81A0, beyond the capacity of the 16K models.

Different rhythms

To make the songs, or even part of them, go to different beats, we are going to add a new 16-bit value: in the upper byte we are going to put $FF, which tells our programme that it is a beat change, while in the lower byte we are going to put the beat, a value from $00 to $0F.

We will create var3.asm and copy all the code from Var.asm into it. We will add two rhythm changes. Locate the Song_1 tag and add below it:

dw $ff0c

When the program encounters this, it interprets it as a beat change ($FF) and expects twelve pauses between each note ($0C).

Locate Song_2 and add to it:

dw $ff06

In this case there will be six pauses ($06) between each note, so we can deduce that the second song will play twice as fast as the first.

Create int3.asm and copy all the code from int2.asm into it. Create test3.asm and copy all the code from test2.asm into it.

We start by modifying test3.asm. In the include we replace var.asm and int.asm with var3.asm and int3.asm and add a comment line to the music tag.

; Bit 0 to 3 -> Rhythm

The rest of the changes are made just before DI by adding the following lines.

ld   a, (hl)
and  $0f
ld   (music), a

Previously we pointed HL to Song_2, now we load the value pointed to by HL into A, LD A, (HL), keep the rhythm bits, AND $0F, and load the value into the flags for the music, LD (music), A.

Let’s go to the int3.asm file and add a new tag at the end, just below the counter, to store the rhythm of the song.

times: db $00

Now we locate the label Isr and five lines below it LD A, (counter). Just above this line we add the following line:

isr_cont:

Four lines down we find CP $06. We change this line to read as follows:

cp   (hl)

Another four lines down we find LD HL, MUSIC. Just above this line we add the next one:

isr_set:

Now we go back to the Isr tag and after the four PUSH we implement the part where we control the rhythm changes.

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

We load in A the value of the indicators, LD A, (MUSIC), we keep the rhythm, AND $0F, we point to HL where we store the rhythm that the song has, LD HL, times, we compare with the rhythm of the indicators, CP (HL), and if they are the same we jump, JR Z, isr_cont, there is no change in the rhythm.

If the rhythm has changed, we store the new rhythm, LD (HL), A, set A to zero, XOR A, and counter, LD (counter), A. Finally we jump, JR isr_set.

This is what int3.asm should look like:

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

All that remains is to modify the Play routine so that it takes the rhythm changes into account. We go to the sound.asm file and after the sixth line, OR E, we add the following lines:

jr   z, play_reset
cp   $ff

With OR E we check if we have reached the end of the songs, in which case we skip, JR Z, play_reset. If we have not reached the end of the songs, we check if it is a change of tempo, CP $FF.

The next line we already had, JR NZ, play_cont, and now what it does is to skip if there is no change of tempo.

We keep adding lines under JR NZ, play_cont.

ld   a, e 
ld   (music), a 
inc  hl
ld   (ptrSound), hl
ret

If we have not skipped, the rhythm will change. We load the new beat into A, LD A, E, load it into the pointers for music, LD (music), A, point HL to the next note (at the frequency), INC HL, update the pointer value to the next note, LD (ptrSound), HL, and exit, RET. The rest of the routine stays the same.

Now it’s time to see how it sounds. We compile, load into the emulator and listen to the results.

pasmo --tapbas test3.asm test3.tap

If all goes well, the two songs will play at different speeds, which you can see by listening or watching the waveform.

ZX Spectrum Assembly, Space Battle

We have done the necessary tests and already know how to implement the music.

In the next chapter of ZX Spectrum Assembly, we will integrate everything seen in this chapter in our game, there will be some small variations, but it is practically the same.

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
A %d blogueros les gusta esto: