GDMonline Programming the PC Speaker, part 1
Phil Inch, Game Developers Magazine

DOWNLOAD ... The example files mentioned in this article are contained in the file SPEAKER.ZIP (7,570 bytes) which can be downloaded by clicking this disk icon.


There are many of us who've been playing with IBM's long enough to remember the time when the only form of sound available was the PC speaker <shudder>.

"With the advent of soundcards
and digital sound, the speaker
has become the poor relation"

The pitiful beeps and squawks which this speaker was capable of generating seemed adequate then, but of course with the advent of soundcards and digital sound, the speaker has become the poor relation for sound generation.

You might wonder why we're covering how to play music on the speaker when most programming languages include commands for doing so (eg PLAY in basic). The simple answer is, what if you want to create sound effects or music in a language which does not have these commands ... like assembler?

And, next month I'll be showing you how to play digital sound using the speaker, which you can NOT do using the standard commands in your programming language, but to understand that article you'll need to understand this one.

To understand how to use the PC speaker, we need to understand a few other elements of how the PC operates, and also remind ourselves of some elementary physics, so here's a brief overview.

High bytes and low bytes

(If you know what these are, please skip to the next section)

A byte is a value from 0 to 255. By combining two bytes, we can create a number from 0 to 65,535. Let's say our number is 'n', and its value is 31798.

The "high" byte is calculated as

	        high = INTEGER( n/256 )
(where the INTEGER function strips off any decimal places)and the "low" byte is then calculated as
	        low = n - high
(ie: it's the remainder).

To combine the low byte and high byte back into one number 'n', we use the formula

	    n = ( 256 * high ) + low 
	      = ( 256 * 124  ) +  54 
	      = 31,798
Two bytes combined in this manner are commonly known as a "word". Four bytes can also be combined, to give us a number in the range 0 - 4,294,967,295 and this is known as a "double word" or "dword" for short.

"It's no good generating sound
effects if no-one except the
family dog can hear them!"

Sound Overview

What is sound? If you've done high school physics, you know that sound is simply a wave. The wave alternately rises (into peaks) and falls (into troughs).

The distance between two peaks is known as the wavelength. When a wave moves from a peak to a trough and back to a peak again, we say that the wave has moved one complete cycle, or oscillation.

The number of oscillations per second is known as the frequency, and this is measured in Hertz, or Hz for short.

The human ear is only capable of hearing a certain range of frequencies, and if I can find my old physics textbooks before I release this mag, I'll print the range.

"It's no good generating
sounds effects if no-one except
the dog can hear them!"

This will be an important consideration when you generate sound. It's no good generating sound effects if no-one (except maybe the family dog) can hear them!

The three system timers

Whenever your computer is operating, there are three "timers" being controlled by various chips inside your computer. These are unimaginatively known as timers 0, 1 and 2.

The frequency at which each timer oscillates is determined by a delay value. The idea is that each timer counts down from this delay value to zero, and when it reaches zero, the timer oscillates. In doing so it raises a signal to let other parts of the computer "know" it has oscillated.

The counter is then re-set to the predetermined value and the process starts again. Each timer maintains its own independant count-down value, meaning that each timer can run at a different frequency.

The counting down process is controlled by the main system oscillator, which runs at a frequency of 1,193,180 Hz, or 1.19318 MHz (MegaHertz). Every time this oscillates, each one of the system timers counts down once. This has nothing to do with the speed of your processor (25Mhz, 33Mhz, etc) - this timer runs at exactly the same speed in *every* PC.

To vary the frequency at which the timers oscillate, you just need to give them a new count-down value. The two formulas used are easily determined:

	                COUNTDOWN = ---------
	                FREQUENCY = ---------

The countdown value can be any value from 1 to 65,535, giving a range of available frequencies of 1,193,180Hz to 18.2Hz.

"You should not mess around
with timer 1 unless you like
your system to crash often"

Timer 0 is the main system timer. It's configured to oscillate 18.2 times every second, by default (therefore, the countdown value is 65,535 from the above formula!).

Whenever timer 0 'ticks', interrupt 8h is generated. Among other things, interrupt 8h is responsible for keeping the clock in your computer going.

This means that if you change the countdown value for timer 0, say to make it run twice as fast (by halving the countdown value), that your system clock will also run twice as fast. This becomes important later on when we play digitised sounds, when we may run the timer hundreds or even *thousands* of times faster than normal!

Timer 1 is used to regularly refresh the contents of your RAM. This timer is of no value to us, but I list it here for completeness. You should NOT mess around with this timer unless you like your system to crash often.

Timer 2 is used to control sound generation. By varying the frequency at which timer 2 oscillates, we can vary the frequency of sound being emitted from the speaker. This requires us to tell the computer to "attach" the speaker to timer 2, as you'll see.

Enough theory, I want to make some noise!

OK, hang on, first you've got to know how to modify the countdown value for timer 2. For the first time in the magazine, we're going to have to go directly to the hardware ports to do this.

[Hardware ports are a bit like pigeon holes for the hardware - by putting certain values into certain ports, we can communicate directly with the hardware. Sometimes the hardware returns values to us which we can retrieve by reading other ports. You'll see more and more examples of port usage as the magazine goes on, particularly from me when I'm running late on a deadline <hic> <grin>]

If you haven't modified ports directly before, you may not know how to do so with your language. I can tell you that with C you can use the 'outport' and 'outportb' functions. With assembler, use the 'OUT' mnemonic. For other languages, I'm afraid you'll have to consult your manual - please let me know what you find.

To tell timer 2 that you want to modify the countdown value, you first have to tell it you're about to do so. You then send the new value as two bytes, the low byte first and the high byte second.

[NB: The pseudocode representation of sending bytes to a port is the OUT command, where we OUT PORT,VALUE. To read a value from a port we use VALUE = IN(PORT)]

First we have to tell timer 2 that we're about to load a new countdown value, which we do by sending the value B6h (dec 182) to port 43h (dec 67). ie:

	            OUT 43h, B6h

Then, in two consecutive statements, we must send the low byte and high byte of the new countdown value, but we send them to port 42h, not port 43h - watch this, it's a common programming mistake!

For example, if our low and high bytes are 54 and 124 respectively, as in our example above, then we do:

	            OUT 42h, 54
	            OUT 42h, 124

(Remember, you are not setting the frequency here, you are setting the countdown value! This is another common programming mistake! Use the formula above to determine the countdown value required for the desired frequency, or vice versa.)

As soon as we have done this, the new countdown value takes effect. However, before this will make any noise, we have to tell the CPU that we want to "connect" the speaker to timer 2, so that every time timer 2 oscillates, so does the speaker, producing a "click".

To do this, we must set bits 0 and 1 of the value on port 61h on. Cue pseudocode:

	            VALUE = IN( 61h )
	            VALUE = VALUE OR 3      (Turn on bits 1 and 2)
	            OUT 61h, VALUE

[If you don't understand the second line, look under 'OR' in the index of your manual. While you're at it, read about 'AND' also because we're going to use that shortly]

To "disconnect" the speaker from timer 2, we need to clear bits 1 and 2 of the value on port 61h:

	            VALUE = IN( 61h )
	            VALUE = VALUE AND 252
	            OUT 61h, VALUE

Note that this connection or disconnection stays put until told otherwise. That is, once you've connected the speaker to timer 2, you can change the frequency as often as you like without doing the connection again.

If you wrote a program to do the above, you would now find that your speaker is oscillating at (1193180/31798) hertz = 37.5Hz. That's just between D and D sharp on the first octave of a piano. (I've included a table of frequencies at the end of the article).

"The possibilities are limitless,
and experimentation is definitely
the name of the game."

To play a little tune, then, we just set up the frequency for a note, wait for a little while, then set up the frequency for the next note, and so on.

To turn the speaker off again, just disconnect it from timer 2 as above, or set the frequency to something inaudible (this is cheating but has the same effect).

To make sound effects, well, there are loads of possibilities. You can quickly move back and forth between two frequencies to generate an "alarm" style sound effect, or you can glide between one frequency and another and back again to generate a "phaser".

"The possibilities are limitless,
and experimentation is the
name of the game"

The possibilities are limitless, and experimentation is definitely the name of the game. You'll be amazed at the sounds you can make if you experiment with changing frequencies rapidly.

To close this month, I've included a program which demonstrates what we've learnt about sound. It's called SOUNDS.EXE, and the source code is in SOUNDS.C.


	 Octave 0    1    2    3    4    5    6    7
	 C     16   33   65  131  262  523 1046 2093
	 C#    17   35   69  139  277  554 1109 2217
	 D     18   37   73  147  294  587 1175 2349
	 D#    19   39   78  155  311  622 1244 2489
	 E     21   41   82  165  330  659 1328 2637
	 F     22   44   87  175  349  698 1397 2794
	 F#    23   46   92  185  370  740 1480 2960
	 G     24   49   98  196  392  784 1568 3136
	 G#    26   52  104  208  415  831 1661 3322
	 A     27   55  110  220  440  880 1760 3520
	 A#    29   58  116  233  466  932 1865 3729
	 B     31   62  123  245  494  988 1975 3951