How To Write ZX Spectrum Games – Chapter 3
Loudspeaker Sound Effects
Note: This article was originally written by Jonathan Cauldwell and is reproduced here with permission.
There are two ways of generating sound and music on the ZX Spectrum, the best and most complicated of which is via the AY38912 sound chip in the 128K models. This method is described in detail in a later chapter, but for now we will concern ourselves with the 48K loudspeaker. Simple it may be, but this method does have its uses especially for short sharp sound effects during games.
First of all we need to know how to produce a beep of a certain pitch and duration, and the Sinclair ROM has a fairly accessible routine to do the job for us at address 949, all that is required is to pass the parameters for pitch in the HL register pair and duration in DE, call 949 and we get an appropriate “beep”.
Alas, the way in which we work out the parameters required is a little tricky as it needs a little calculation. We need to know the Hertz value for the frequency of note to emit, essentially just the number of times the loudspeaker needs to be toggled each second to produce the desired pitch. A suitable table is located below (# stands for ‘sharp’):
Middle C 261.63
For each octave higher, simply double the frequency, to go an octave lower halve it. For example, to produce a note C one octave higher than middle C we take the value for Middle C – 261.63, and double it to 523.26.
Once the frequency is established we multiply it by the number of seconds required and pass this to the ROM routine in the DE register pair as the duration – so to play the note at middle C for one tenth of a second the duration required would be 261.63 * 0.1 = 26. The pitch is worked out by first dividing the 437500 by the frequency, subtracting 30.125 and passing the result in the HL registers. For middle C this would mean a value of 437500 / 261.63 – 30.125 = 1642.
In other words:
DE = Duration = Frequency * Seconds
HL = Pitch = 437500 / Frequency – 30.125
So to play note G# one octave above that of middle C for one quarter of one second:
; Frequency of G sharp in octave of middle C = 415.30 ; Frequency of G sharp one octave higher = 830.60 ; Duration = 830.6 / 4 = 207.65 ; Pitch = 437500 / 830.6 - 30.125 = 496.6 ld hl,497 ; pitch. ld de,208 ; duration. call 949 ; ROM beeper routine. ret
Of course, this routine isn’t just useful for musical notes – we can use it for a variety of effects as well, one of my favourites being a simple pitch bend routine:
ld hl,500 ; starting pitch. ld b,250 ; length of pitch bend. loop push bc push hl ; store pitch. ld de,1 ; very short duration. call 949 ; ROM beeper routine. pop hl ; restore pitch. inc hl ; pitch going up. pop bc djnz loop ; repeat. ret
Have a play with the above routine – by fiddling with it it’s pretty easy to adjust the pitch up and down, and to change the starting frequency and pitch bend and length producing a number of interesting effects. One word of warning though – Don’t go too crazy with your pitch or duration values or the beeper routine will get stuck and you won’t be able to regain control of your Spectrum without resetting it.
When using the loudspeaker we don’t even have to stick with the routines in the ROM, it is easy enough to write our own sound effects routines, especially if we want to generate white noise for crashes and bangs. White noise is usually a lot more fun to play with.
To generate white noise all we need is a quick and simple random number generator (a Fibonacci sequence might work, but I’d recommend stepping a pointer through the first 8K of ROM and fetching the byte at each location to get a reasonably random 8-bit number). Then write this value to port 254. Remember this port also controls the border colour so if you don’t want a striped multicolour border effect we need to mask off the border bits with AND 248 and add the number for the border colour we want (1 for blue, 2 for red etc.) before performing an OUT (254) instruction. When we’ve done this we need to put in a small delay loop (short for high pitch, long for lower pitch) and repeat the process a few hundred times. This will give us a nice “crash” effect.
This routine is based on a sound effect from Egghead 3:
noise ld e,250 ; repeat 250 times. ld hl,0 ; start pointer in ROM. noise2 push de ld b,32 ; length of step. noise0 push bc ld a,(hl) ; next "random" number. inc hl ; pointer. and 248 ; we want a black border. out (254),a ; write to speaker. ld a,e ; as e gets smaller... cpl ; ...we increase the delay. noise1 dec a ; decrement loop counter. jr nz,noise1 ; delay loop. pop bc djnz noise0 ; next step. pop de ld a,e sub 24 ; size of step. cp 30 ; end of range. ret z ret c ld e,a cpl noise3 ld b,40 ; silent period. noise4 djnz noise4 dec a jr nz,noise3 jr noise2