Polyphonic Music on the IBM PC Steve Muenter Rocketdyne Microcomputer Users Group Sitting at the keyboard of my IBM PC, I realized that other computers could produce music with three simultaneous tones for chords and harmony while my IBM was limited to only one tone at a time. To rectify this, I wrote an assembly language program which produces three tones from the IBM PC's internal speaker without any hardware modification. Normally, a single tone is generated through the speaker using the 8253 Programmable Interval Timer. The microprocessor loads a divisor into the 8253's register for counter 2. The 1.19 MHz system clock is divided by this number and the resulting square wave is sent to the speaker. Once the divisor is loaded, the microprocessor is free to do something else while the tone plays in the background. Since the hardware is not set up to produce three tones simultaneously, it becomes necessary to get the microprocessor to do the dividing of the system clock. The program loop which performs the divisions for the three tones must be very quick in order to produce tones with high audio frequencies. I found when using memory to store intermediate division results, the relatively slow memory accesses took too many clock cycles giving three low growls at best. Using the 8088 internal registers speeded up the loop considerably since the register accesses are about ten times faster than memory accesses. The assembly language routine presented here was written to be called from BASIC. The code is divided into three sections. The first section (from "tri" to "sort") sets up the pointers to the data passed to the routine. Also, the 8088 internal registers are zeroed. The second section (from "sort" to "loop") sorts the 16-bit integer numbers passed to the routine. The three most significant bits of the integer identify the remaining 13 bits as either voice 1, 2, or 3 period data, note duration data, tempo data or end-of-song flag. The bit pattern 000 identifies that the song has ended and causes the routine to return to BASIC. The pattern 001 sets the duration of time that the current notes will play before the next notes start. The pattern 010 sets the tempo of the playback. The pattern 100 identifies that the 13 remaining bits represent period data for voice 1. The pattern 101 identifies voice 2 period data, and the pattern 110 identifies voice 3 data. The last section (from "loop" to "end") performs the division of the system clock and switching of the speaker for each of the three voices. This is the section of the program that actually makes the tones. For speed, the routine for each voice is written entirely rather than written as a single subroutine called three times. This was done because jumps and calls require at least fifteen clock cycles. Although the assembly language routine parses the 16-bit binary data into three bits identifying data type and thirteen bits representing a numerical value, BASIC treats this data as an array of 2's complement 16-bit single precision integers. This means that the decimal equivalent of the number is given by the following equation. N = -2^15*a(15) + 2^14*a(14) + 2^13*a(13) + 2^12*a(12) + 2^11*a(11) + 2^10*a(10) + 2^9*a(9) + 2^8*a(8) + 2^7*a(7) + 2^6*a(6) + 2^5*a(5) + 2^4*a(4) + 2^3*a(3) + 2^2*a(2) + 2^1*a(1) + 2^0*a(0) where a(n) is a 1 or 0 in the nth bit of the 16 bit number. Notice that the most significant bit (the 16th bit) represents a negative number (-32768) whereas all the other bits represent positive numbers. The numerical range of the 2's complement integers is -32768 to 32767. It can be seen that the pattern of the three most significant bits can be represented as a decimal number. The pattern for identifying duration data (001) corresponds to a decimal value of 8192. Tempo data (010) equals 16384, voice 1 data (100) equals -32768, voice 2 data (101) equals -24576, and voice three data (110) equals -16384. The 13 remaining bits can represent decimal numbers ranging from 0 to 16383. The accompanying BASIC program contains data statements which load an integer array with the data for playing the first part of the Rondo Alla Turca by Wolfgang A. Mozart. The first entry in the integer data array sets the tempo of the playback. A typical tempo value of 512 plus the tempo identifier 16384 gives the integer data value 16896. A note's duration is specified by its length relative to a 32nd note. For example, a quarter note equals eight 32nd notes, so a decimal value of 8 is added to the duration identifier, 8192. The resulting number is 8192 + 8 = 8200. Likewise, a whole note is represented by the decimal integer 8192 + 32 = 8224. The voice data is determined by adding the number representing the frequency of the desired note to the number identifying the desired voice of the three possible. The following table gives the numbers representing the notes of the scale. For example, middle C on voice 1 is represented by adding the voice 1 identifier with the value for middle C. The resulting number is -32768 + 1024 = -31744. To turn voice 1 off, the data would be -32768 + 0 = -32768. NOTE DATA TABLE LOW MIDDLE HIGH --- ------ ---- C = 512 C = 1024 C = 2048 C# = 542 C# = 1085 C# = 2170 D = 575 D = 1149 D = 2299 D# = 609 D# = 1218 D# = 2435 E = 645 E = 1290 E = 2580 F = 683 F = 1367 F = 2734 F# = 724 F# = 1448 F# = 2896 G = 767 G = 1534 G = 3069 G# = 813 G# = 1625 G# = 3251 A = 861 A = 1722 A = 3444 A# = 912 A# = 1825 A# = 3649 B = 967 B = 1933 B = 3866 No tone = 0 [Editor's note : The programs discussed above can be found on this diskette as separate files, TRI.BIN, TRI.ASM and TRTEST.BAS. The programs do not operate properly on the PCjr or the PC AT due to timing considerations.]