Andrew Hewson analyses sound and lists a simple security program
WHAT IS the formula for calculating the frequency and duration of a note as needed by the ROM routine at 03B5? asks Richard Hyde, who wants to use a Spectrum to create music.
Before answering the question I shall describe how the Spectrum BEEP command works. Chapter 19 in the manual explains that the command has two parameters which determine the duration and pitch respectively of the note to be produced. The duration is measured in seconds and the pitch is in semitones relative to middle C. Thus the command:
causes the computer to play C sharp - one semitone above middle C - for half a second.
Three separate ROM routines are used when a note is to be played. The first is the command interpreter which determines that it is the BEEP command which is to be executed, as opposed to PRINT or DIM. It also places the two parameters in floating point form on the calculator stack. It then passes control to the BEEP command routine located at address 03F8.
That routine converts the duration and pitch parameters to two new numbers. The new parameters are passed in the DE and HL register pairs to a third routine located at 0385 which drives the loudspeaker, causing the correct note to be produced.
The third routine drives the loudspeaker by toggling - i.e., switching rapidly on and off - the low voltage supply to the loudspeaker electromagnet. That causes the cone of the loudspeaker to vibrate, thereby generating an audible sound. The two parameters passed to the routine control the number of times the toggling takes place and the rate of repetition.
Thus the answer to Hyde's question is in the BEEP command routine and in particular in the conversion of the pair of parameters but before considering the routine in detail a digression on the mathematics of music is necessary. To assist the non-musical reader some basic terms are defined in table one.
An octave is a "natural" separation between two notes which most people can detect readily. Raising a note by an octave is equivalent to doubling the frequency of the note. For example, while the frequency of middle C is about 262 cycles per second, the frequency of the note an octave higher, called upper C, is about 524 cycles per second. Similarly the frequency of the note an octave higher still is about 1,048 cycles per second.
If all the notes ever played were octaves of middle C, music would be very uninteresting and the average piano keyboard would have about seven notes. Hence to add variety each octave is divided into 12 semitones. Those semitones are approximately evenly spaced in the logarithmic scale of frequency, i.e., the difference of the logarithms of the frequency of adjacent pairs of semitones is approximately the same. Each of those semitones are related octave notes above and below, thus generating the 70-odd notes on the piano keyboard.
It would be possible to create all notes from a single reference frequency, stepping up or down by the required number of semitones by adding or subtracting the logarithmic increment the required number of times. Sinclair has chosen not to do that. Instead, the frequencies of all 12 semitones in a single reference octave are stored in floating point form in ROM at address 046E. Higher notes are obtained by doubling or quadrupling those frequencies, lower notes by halving or quartering.
The frequencies and the names of the notes are listed in table two. The logarithm to base 10 of each frequency is also listed in table two and it is clear that the difference in each adjacent pair of logarithms is about 0.025.
The BEEP command routine uses the table to calculate the required frequency from the pitch parameter as follows:
The required semitone is determined from the result of the calculation
SEMITONE = 12*INT (PITCH/12)
That gives a value in the range of 0.0 to 11.99. The integer part of that number determines which semitone is selected. The frequency is then adjusted upwards using an approximate calculation to take account of any non-integer remainder.
The required octave is determined from the calculation
OCTAVE = INT(PITCH/12)
The frequency obtained from the first part of the calculation is then doubled or halved the appropriate number of times. For example, it is doubled once if OCTAVE = 1, twice if OCTAVE = 2, halved once if OCTAVE=-1.
To complete the conversion of the first parameter, the frequency obtained, measured in cycles per second, is multiplied by the duration parameter, in seconds, to give the total number of times the loudspeaker supply line must be toggled. The result is passed to the BEEPER routine at 03B5 in the DE register pair.
The second parameter passed to the BEEPER routine controls the interval of time between each toggling of the loudspeaker line. That time interval is not measured in seconds but in the number of "T states" divided by four for which the BEEPER routine is to remain in a delay loop before switching the loudspeaker line on or off again. A T state is the period of time the Z-80 microprocessor takes to execute the fastest instruction. In the Spectrum there are 3,500,000 T states per second. The steps in the calculation are:
Divide 3,500,000 by the frequency of the required note; divide the result by four; subtract 30.125 to take account of overheads at the beginning and end of the delay loop. The result is passed to the BEEPER routine in the HL register pair.
I have written previously that POKEing numbers into RAM is the equivalent of putting a spanner in the works. POKEing causes no permanent harm but you should not be surprised if you cause the Spectrum to crash.
Peter Read stumbled on a useful facility by POKEing at random. He writes: If you enter a clear command and the POKE zero into the next but one and the next but two lower bytes in RAM, the Spectrum will continue to work normally until it reaches the end of the program or the break key is pressed at which point it will crash. Why? It is not necessary to enter the CLEAR command to obtain that effect. CLEAR resets RAMTOP to a new address. RAMTOP in the 48K Spectrum is set initially to 65367 and the short program listed in table three will function normally until it finishes or the break key is pressed, at which point it will crash, as Read describes.
The reason for that effect is that the two locations altered by the POKE commands together contain the address to which the processor jumps when an error occurs. That fact can be demonstrated by examining the ERR SP system variable held at location 23613. In the 48K Spectrum that system variable contains 65364, which is the address of the lower of the two locations which are POKEd in the program. Thus ERR SP points to the address which points in turn to the address to which an error jump is made. The procedure seems complicated but it provides a system with a great deal of flexibility.
The locations normally contain 3 and 19 respectively and so the address to which they point is:
Address = 3 + 256*19 = 4867 = 1303 (hex).
That location is in the middle of the main execution routine. The Spectrum then prints the error number and awaits an input from the keyboard.
The effect of resetting those two addresses to zero is to cause the Spectrum to jump to ROM address zero, the equivalent of pulling out the power supply plug and starting again. Hence that gives a useful way of protecting your programs against unauthorised inspection.