Making Music using the PC Speaker (Win9x or DOS)

PCs have always had a primitive form of musical ability, which revolved around the onboard speaker connected to the motherboard. This speaker can be "plucked" automatically by the sound hardware built into PCs, at specific frequencies.

The act of plucking a speaker is essentially sending a pulse to the speaker hardware at a specific frequency. Everyone at some point has taken a pair of headphones or one of those 2 inch speakers found in cheap transistor radios and attached the leads to the speaker or headphone to a 9 volt battery ... the momentary changes in voltage applied to the leads of the speaker cause the speaker to move the cone in and out, making a crackling sound. If you were able to control the frequency of the applied voltage so that, for example, you caused the cone to move in and hout 262 times per second, you would generate the same tone as middle C on a piano!

As it happens, the PC is capable of making controlled pitch sounds quite easily, with the help of a bit of input/output software written in 80x86 assembly.

To generate sound using the internal PC speaker hardware, we need the following I/O ports:

Port 61h - Speaker Control Port

7  6  5  4  3  2  1  0
x  x  x  x  x  x  A  A

where A = 0 to turn speaker off, 1 to turn speaker on
note: all other bits MUST NOT BE CHANGED! Thus, read, adjust, write!

Port 43h - Frequency Adjust Enable

Write the value 86h into this port to allow changes to timer frequency.

Port 42h - Frequency Value

This port is written twice ... the first time, it's written
with the LOW byte of the frequency short integer, and the
second time, with the HIGH byte. This is a common trick in
hardware to allow a CPU to "send 16 bits of data via an 8 bit
I/O port".

Input/Output port 61h is the control port for the speaker sound hardware. By adjusting ONLY bits 0 and 1 of this port, you can turn on/off frequences playing on the speaker via software.

To turn on the sound, set bits 0 and 1 of port 61h to a value of binary 1. To turn off the sound, reset bits 0 and 1 of port 61h to a value of binary 0. You MUST NOT adjust any of the other bits, therefore, you have to use AND and OR intelligently to set or reset the appropriate bits without affecting the other bits in this port.

Thus, you have to first input the value from this port, modify the two lowest bits using AND or OR depending if you are setting them to zero or setting them to one, and then write the value back out to hardware.

The frequency of the sound is specified using a built in timer mechanism, which will "pluck" the speaker at a pre-determined frequency, specified by the value written to the timer hardware.

The timer in question uses a base frequency of 1,192,180 Hz, and the number you program into this timer mechanism is a divisor to adjust how often the "pluck" will occur. This is the timer counter limit, and causes the timer to pluck the speaker when the counter reaches this limit value.

For example, writing a value of 4554 to the timer hardware will cause the plucking frequency to be lowered from 1,192,180 Hz lowered to approximately 262 Hz, as the timer hardware now needs to count from 0 to 4554 before a "pluck" occurs. This divisor therefore allows you to vary the frequency of plucking, thus producing musical notes!

To adjust the timer counter limit, you must first adjust write the value 86h to port 43h to indicate a value is about to be changed in the timer hardware.

Next, we need the LOW BYTE of the 16 bit integer corresponding to the upper limit. For the value 4554, if we write this value in hex, we would have 11CA. Thus, the UPPER BYTE is 11h, and the lower byte is CAh.

Write the low byte into port 42h. Next, write the upper byte into the same port, 42h. It appears we've overwritten the value, however, the hardware knows that two subsequent writes to this port are meant to be treated as a pair (low byte, high byte), and hence, the internal hardware timer will load the values correctly.

At this point, you can set the low two bits of port 61h to 1's, and hear your music! When you're sick of your sound, reset the low two bits of port 61h to 0's, and the sound is turned off.

Calculating Frequencies and Timer Upper Limits

Middle C on the piano is equivalent in pitch to a sine wave of frequency 262 Hz. The frequencies of each note in a normal C major scale can be calculated by using the chart below:

Note Name Multiplier
---- ---- ----------
 C    do   1.00
 D    re   1.12
 E    mi   1.26
 F    fa   1.33
 G    so   1.49
 A    la   1.68
 B    ti   1.88
 C    do   2.00

Thus, to determine the frequency of F (fa), take the middle C frequency of 262Hz and multiply by the factor 1.33. This generates a frequency of 348Hz.

Note that one octave of the scale doubles in frequency. Thus, the C below middle C will be 1/2 the frequency of middle C, or 130Hz. The 2nd C above middle C will be approximately 1048Hz.

Hence, you can use the multiplier values above to essentially generate a C major scale from the low end of the piano scale to the upper end quite easily, as it's a simple set of calculations to determine the frequencies of all the "white keys" on the piano.

The black keys are different multipliers from the starting C value, and doing a search on the Internet should allow you to determine what those multipliers should be.

Here's a nifty little graphical version of the table above:

Piano Keyboard Frequencies

http://www.bestpianolessons.com/piano-images/piano-keys/piano-keys.jpg

In the graphic above, middle C is represented by C4.

To generate the timer upper limit, we need to take the base frequency of the timer, which is 1,192,180 Hz and divide by the frequency of the note in question. For example, middle C is 262Hz. Dividing this into our base frequency gives us the counter value of 4554 (round to the nearest value). Thus, you can build up a table of all the divisor values in software by programming some code that references the frequency multiplier for a given note, multiplied against the base frequency of C, and then dividing that frequency value into the base frequency of the timer. Or hard code a table with ALL of the values as shown in the image above. This type of information need not be manually calculated in software - frequences for a piano scale NEVER change, thus, implement a table with all the values and do a simple table lookup to get the frequency info you need!

Tips for Assignment #4 (choice #2)

Create a simple subroutine that can play a note of a specified frequency for a specified amount of time.

Pre-calculate all frequency values IN SOFTWARE, based on the multiplier table. You can start with one octave (middle C to C above middle C).

Let the keys on the keyboard act as the "white keys" of the piano. Use the upper case letters of the alphabet correspond to the notes in our table above. Thus, if you type 'F' on the keyboard, you should be able to determine what the frequency of F is, and output that sound. Use the letter 'X' to represent the C above middle C (or lower case 'C' if you prefer).

Have each sound play for 1 second.

To generate the appropriate delay, use the Windows API function

	Sleep(1000);	// parameter is the delay in millseconds

Consult your notes from last week on how to access Windows functions via assembly.

You should be able to perform most of the work of this application in assembly snippets. Remember, you can create C functions that perform work in assembly, thus, you may want to consider creating functions such as:

	char get_a_character_from_keyboard();

	void play_note (int timer_upper_limit, int seconds);

and encapsulate your assembly code into the appropriate functions to support the needs of this program.

If you work in small teams, it would be fun to have each team create the "piano simulation" solution, and actually play some simple tunes. For example, here are some basic melodies (where C' is the C above middle C):

Happy Birthday
C C D C F E C C D C G F C C C' A G F E B B A F G F

Twinkle Twinkle
C C G G A A G F F E E D D C (and so on)

Row Row Row Your Boat
C C C D E E D E F G C' C' G G E E C C G F E D C

When you think about this concept, you'll soon realize that those "musical" greeting cards, or even ring tones on a Cell Phone, are nothing more exciting than the software you've just created!

For more exotic control over your sound capabilities, can you modify your source code to accept an input file that encodes a song using a text file, formatted in this fashion:

BEAT: 1000
C: 1
C: 1
D: 2
C: 2
F: 2
E: 4
and so on, so that you can specify the relative length of each note. Above, we have C played for "one beat", the F played for "two beats", and E played for "4 beats". If we use our one second concept above, this would translate to 1 second, 2 seconds and 4 seconds. Note that this would be a really SLOW song, so having the ability to modify the beat delay would be useful. Here, we have the beat delay set to 1000 milliseconds, or 1 second. To make the song play faster, adjust that downwards, say, to 250 milliseconds (1/4 second). This will make the song play that much quicker.

Add in support for "rests" so that you can play a "silent" note for a given time duration.

With luck, you'll have a fully functional "player piano" capability, and you've mastered input/output with a really fun and interesting software mechanism!

Have fun with this!