Here in this chapter I explain routines for a VSYNC interrupt driven
custom
sound player. This means that you can create your own sound effects
that are still playable with playsound. It is possible to keep
or to replace the built-in tunes. The piece of code shown here is used in
several variants by a lot of the commercial games. The one I explain here
supports loading the sound shift registers with any value which is a
feature that is only used by a few of the commercial games. Most game leave
out the code handling it. The demo tune included into the full example does
not use it, I explain it just to be complete.
The sound hardware is just a 24 bit shift register with one end
connected to the sound output. This 24 bit register is split into the
3 separate byte wide registers vdc_sound0/1/2. The sound
is controlled by the vdc_soundctrl register. The highest bit
switches the sound output on or off. The next bit enables a loop mode, the
sound output will be rotated back into the other end of the shift register
when this bit is set. Otherwise the sound will stop after all 24 bits are
sent. Bit 5 is used to select between two different shift frequencies.
Another source of sound is a noise generator enabled with bit 4. The
volume of the sound output is controlled by the last 4 bits inside the
vdc_soundctrl register.
The number of sounds the hardware can create is very limited and creating a tone with a certain frequency is beyond the scope of the BIOS code. It is possible to generate a certain frequency by modulating a noise signal: Generate an exact timing with the timer and a software timing loop and toggle the sound enable bit, this is used by Videopac 31 (Musician). But here I explain tunes that the BIOS routines can generate.
The tunes played by the BIOS use commands interpreted by the BIOS tune
player as long as bit 6 of iram_irqctrl is set. The
pointer to the current tune command is set by playsound. This
pointer points into bank 3, so the tune for the "SELECT GAME" sound
starts at 034Ah. The custom tune player just gets the data from another
bank. It is obviously possible to overwrite the built-in tunes as desired,
but this example keeps them intact. Every VSYNC interrupt the tune player
is called, but most of the commands have a duration. Only after this
duration is over a new command is loaded by a jump to 040Ah, which normally
jumps to soundirq.
There are 4 commands supported by the BIOS. Three of the commands have a parameter. The commands are differentiated by the highest bit that is set. The custom tune player here supports another sound opcode to remove a limit by the BIOS routines that is only relevant when creating custom tunes.
If bit 7 of the command byte is set a new tone starts. The duration
of this command is the command byte with bit 7 cleared. The parameter
byte is a pointer to the new contents for the shift registers and
vdc_soundctrl. They are always read from bank 3. There
are 10 different tones from 0300h-0327h, but the BIOS tunes only use 8 of
them. If you want more than those 10 tones you have to create your own
opcode as shown later.
If bit 7 of the command byte is 0 and bit 6 is set the
control register is loaded. The duration is the command byte with bit 6
cleared. The parameter byte is the new value for
vdc_soundctrl. This command is used to change the sound volume
and to play sound with noise.
If the bits 7 and 6 are 0 and bit 5 is set the sound is turned off. The duration is the command byte with bit 5 cleared. This command does not have a parameter byte.
If the bits 7, 6 and 5 are 0 and bit 4 is set
the current tune position is set to the parameter byte and the tune player
is restarted. It is possible to create endless sounds with this opcode. To
play another sound just call playsound again.
If the BIOS does not recognise a command it turns the sound off and stops playing. The BIOS tunes use 0 for this.
If this custom tune player reads a command byte 00Fh it turns off any
sound and copies the next 3 bytes into the sound shift register. After
that the tune player is restarted, the next command executes immediately.
This command should load vdc_soundctrl, because the sound is
still turned off from loading the shift register.
As usual I only show the relevant parts of the program, the full program can be downloaded as usual.
To create custom sound tunes the first part of the BIOS sound IRQ has to be replaced, the place to do this is at 040Ah:
jmp selectgame ; RESET
jmp irq ; interrupt
jmp timer ; timer
jmp vsyncirq ; VSYNC-interrupt
jmp start ; after selectgame
jmp mysoundirq ; sound-interrupt
The 8048 is still in IRQ mode, so SEL MB1 is not possible
and the BIOS has switched to RB0. First we have to see if the tune
currently played is one of the built-in tunes. Here I leave all built-in
tunes intact, the new ones start at 076h. The current tune position is
delivered in R4, if it is smaller than 076h the old BIOS routine is called.
mysoundirq
; check if BIOS sound or custom sound
mov a,r4
add a,#08ah ; >= 076h
jc .custom
jmp soundirq ; BIOS tune
Now I just read a byte as command byte and test for the new 00Fh opcode. If
it is not the new opcode I read another byte as parameter byte. The rest is
handled by the BIOS in parsesnd.
.custom ; custom sound handler, read sound opcodes from current page
mov a,r4
movp a,@a
mov r1,a ; command byte
inc r4
xrl a,#0fh
jz .op0f ; test for new opcode
mov a,r4
movp a,@a
mov r2,a ; parameter byte
jmp parsesnd ; let BIOS sound IRQ handle opcode
There is no BIOS routine for the new opcode 00Fh, so I have to do it all by hand. First the sound gets turned off, because I change the sound shift register. Then the next 3 bytes are copied into the shift register and the sound IRQ handler is started again to execute the next sound opcode.
; opcode 0F: sound off, copy next 3 bytes into A7/8/9
.op0f mov r0,#vdc_soundctrl
clr a
movx @r0,a ; old sound off
mov r0,#vdc_sound0
mov r1,#3 ; number of bytes to copy
.loop mov a,r4
movp a,@a
movx @r0,a
inc r0
inc r4
djnz r1,.loop
jmp mysoundirq ; restart sound handler