SOUND BEYOND SOUND
Breaking the BASIC barrier
by CHRIS CHABRIS
ATARI sound capabilities are very strong, compared to most other small computers, and are completely at the disposal of the programmer. Unfortunately, that access is not well explained in the Atari BASIC Reference Manual. You should reread pages 57-58 of the manual to fully benefit from this article, but I intend to take you well beyond the basics, show you some techniques for better use of sound, and finally give you a "sound editor" program to test and select various sounds for use in your programs.
First, let's examine the SOUND command in BASIC, to see how it works, and how we can avoid its limitations.
Sound is generated on ATARI computers by the chip called POKEY (an acronym for POtentiometers and KEYs), a cousin of the GTIA and ANTIC chips. POKEY is capable of routing four individual sound frequencies through the television speaker at any one time. Each frequency is associated with a value ranging from zero to 255, and so can be specified in one byte of memory. These independent frequencies are known as "voices" or "sound channels" because they play one tone at a time.
The frequency generated by each voice is affected by a control value for that voice, also a one-byte number. Packed into the control value are the distortion and volume parameters of the BASIC SOUND command. Distortion, used primarily to cause sound effects, ranges from zero to 14 (even values only), with 10 considered as "pure." Volume ranges from zero to 15, with eight considered normal.
How do these numbers reach POKEY so that it can generate the proper sounds? The POKEY chip itself is controlled by several hardware registers, which are like memory addresses on the chip. Values can be POKEd into them just like normal memory addresses. However, the value obtained by PEEKing into these registers is usually not the same as the one previously POKEd in. Generally, each register has two functions - one when it is read and another when it is written to.
As you may have expected, four of these registers hold the frequency values for the four voices and four of them hold the control values. Incidentally, when PEEKed, these registers will give you the instantaneous values of the eight Paddle Controllers, just as does the PADDLE command in BASIC.
Now let's try generating some sounds by POKEing these registers directly. The command SOUND 0,19,10,8 will produce a high C; note of pure distortion and moderate volume on channel number one. How could we duplicate this with POKE commands? Consulting Table 1, we find that the frequency is POKEd into 53760. So type:
POKE 53760,19
The control value poses a greater problem. In order to get the distortion and volume numbers into one byte, use the following formula:
CONTROL = 16 * DISTORTION + VOLUME
Therefore, the control value is 16*10 + 8, or 168. The control register for voice zero is 53761, so the second POKE statement is:
POKE 53761,168
If you turned off the original sound with a SOUND 0,0,0,0 command, you should now hear the same tone again.
Try the same experiment with other values, comparing the results obtained with SOUND and POKE. They will be identical.
Now let's consider one of the deficiencies of the Atari BASIC SOUND command, the lack of a duration parameter (I understand that the SOUND command in Atari Microsoft BASIC has such a parameter). To get around this deficiency, routines that play music or sound effects are often timed by FOR/ NEXT loops. For example:
100 SOUND 0,128,10,8
110 FOR L=1 TO 100:NEXT L
120 SOUND 0,0,0,0
This would play a low B note for whatever length of time it took the loop to execute. If we could make a table relating the lengths of empty loops to their execution times, this would be a perfect method of timing sounds. Unfortunately, we can't.
The speed of a FOR/NEXT loop depends on its position in the program; it is slowest at the end of a program and fastest at the beginning. The difference is especially noticeable in Iong programs. Try it yourself.
Luckily, there is an accurate way of measuring the duration of sounds. Every sixtieth of a second, memory location 540 decrements by one until it reaches zero. Then it is restored to 255, and starts to count down again. Therefore, if we want to play a middle C note for one second, we could use the following code:
100 SOUND 0,121,10,8
110 POKE 540,60
120 IF PEEK(540)>0 THEN 120
130 SOUND 0,0,0,0
This works very well (you can verify it with a stopwatch), and allows songs to be played with perfectly timed notes and rests. Location 540 may skip a count under certain conditions, but this is unlikely to occur in a BASIC music-playing or sound, effects routine.
There is another problem with using any of the techniques here to play music in a program. If you use a subroutine to play a song, all other action in the program must stop while your routine executes. This can be tens of seconds with a long piece, causing a long interruption.
As usual, there is a solution -- the vertical blank interrupt (VBI). This occurs almost precisely every sixtieth of a second, when the electron beam drawing the screen display reaches the lower right corner and must return to the upper left. The screen actually goes blank at this time, although it happens too fast for us to see. Whatever program in whatever language is executing at the time stops, and a different one begins. When its execution is complete, the main program resumes exactly where it stopped without knowing what happened.
This "secondary program" consists of two stages: immediate and deferred. The ATARI has an immediate routine built into the Operating System (OS) which maintains the real-time clock and timers (such as location 540), reads the controller ports, and performs other miscellaneous tasks.
Fortunately, there is no real built-in deferred VBL routine, so we can insert our own. Since the VBI routine is transparent to the main program, it can change the sound registers on POKEY and maintain durations in sixtieths of a second without visibly interfering with the applications program (Wouldn't you like a little bagkground music during a long word processing session!).
There is one problem: a VRI routine must be written in machine language. Since most of you probably don't program in machine language, I have provided such a routine that you can incorporate in your own programs. It consists of three parts: the actual deferred VBI routine, which is POKEd into Page Six of memory (a reserved area); a short program, contained in a string, that makes the OS aware of our routine; and a similar program to turn off the VBI routine.
Listing 1 is a BASIC loader program to set up the necessary machine language. It ends with a RETURN command, so you can use it as a subroutine in your own programs.
The music-playing VBI is very easy to use in BASIC. Before using it, execute a GOSUB 20000 command to load all of the necessary machine language into memory. Now, you must POKE in several parameters. Table 2 is a list of the various memory locations that hold these parameters. First, there is the number of voices that your music requires: 1, 2, 3, or 4. This is POKEd into memory location 1712. Next, for each voice, you must set the address of the music to be used with the following code:
V1A = ADR(V1$)
V1AH = INT(V1A/256)
V1AL = VIA-256*V1AH
POKE XXXX,V1AL
POKE XXXX + 1,V1AH
V1$ holds the music table for voice number one and XX is the address on Page Six that holds the address of the string containing the music (see Table 2). These lines should be repeated for each voice (V2$, V3$, V4$).
A music string contains values for each note grouped in pairs of Frequency and Duration (in sixtieths of a second). The frequencies 255 and 254, however, have special meanings and therefore cannot be used to specify notes. A 255 tells the VBI routine to completely turn off the voice whose string contains that byte. A 254 tells the routine to repeat the music for that voice from the beginning. One of the limitations of the routine is that a music string can be a maximum of 255 bytes long. Since each note requires two bytes, a voice may play a maximum of 127 notes before they either repeat or end. Most songs that would be used in programs are shorter than this, so it should not be a big problem.
If you want to turn off a voice, POKE a one (1) into its status address, locations 1725 through 1728 for the four voices. POKE a zero into a status address to restart the voice where it left off.
To completely end all voices and turn off the VBI routine, execute the following code:
Q = USR(ADR(RESET))
FOR L=0 TO 3
SOUND L,0,0,0
NEXT L
Pressing [SYSTEM RESET] will also kill the VBI routine. Remember, this information is summarized in Table 2.
To help you start using this system in your own programs, I have included Listing 2, a demonstration program using one voice to play the theme from a familiar movie. When merged with Listing 1 (through the use of the ENTER command), it will produce a skeleton program that you can modify and incorporate directly into your own code. It is documented by REMark lines and includes all necessary POKE commands. Have fun!
There is one more POKEY register to discuss, and it single- handedly gives the ATARI greater sound capabilities than most, if not all other personal computers. Location 53768, known as AUDCTL (For AUDio ConTroL), provides you with eight additional options which affect all four sound channels. Table 3 is a summary of the functions available with the AUDCTL register. Each bit of AUDCTL enables an option when set to one and disables it when set to zero. To find the number to POKE into AUDCTL, add up the numbers to the left of each option that you want to enable. These options are explained in greater detail below.
Poly counters provide random pulses used to generate the distortion in the sound channels. By setting bit 7, you can make the patterns set by various distortion values more regular, because long polys do not generate obvious repetition.
Clocking a channel with 1.79 MegaHertz (millions of cycles per second) will simply result in a much higher pitch, because the normal clock base is set at 64 KHz, or 64,000 cycles per second. Bits 6 and 5, when set, change the clock base for voices one and three only.
POKEY normally has a frequency range of zero to 255, allowing for about four octaves. Setting bits 4 and 3 of AUDCTL joins two voices so that their combined frequency ranges from zero to 65535 (known as 16-bit resolution), resulting in a nine-octave range of pitches. The control registers of channels two and four determine the distortion and volume for the joined pairs "one and two" and "three and four." Of course, you cannot use the SOUND command with 16-bit music. The following routine will cycle through the 16-bit frequency range:
100 SOUND 0,0,0,0
110 POKE 53768,16
120 POKE 53761,0:POKE .53763,170
130 FOR LO = 0 TO 255
140 POKE 53760,LI
150 FOR L1=0 TO 255
160 POKE 53760,LI
170 NEXT LI
180 NEXT LO
190 GOTO 100
At the end of the loop, when both LI and LO are 255, the output frequency is approximately one Hz, so you hear a low pop every second. Change the POKE value in line 110 to 80 and observe the difference. This switches the channel-one clock to 1.79 MHz and produces higher quality low notes. Remember that you should always execute a SOUND 0,0,0,0 command before the sound routine in your programs, in order to properly initialize POKEY.
Setting bits 2 and 1, enabling high-pass filters, allows only high frequencies to pass through a sound channel. The lowest frequency that can pass is defined as one more than the frequency of the clock channel. The following program uses a high-pass filter to produce an interesting sound, somewhat like a "red alert" on a spaceship:
100 SOUND 0,0,0,0
110 POKE 53768,4
120 POKE 53761,170
130 POKE 53764,150
140 FOR L = 255 TO 0 STEP -1
150 POKE 53760,L
160 NEXT L
170 GOTO 140
Other special effects can be created by varying the clock frequency while the filtered voice is playing.
The last option available, enabled by setting bit 0, is switching the clock base for all four voices from 64 KHz to 15KHz. With this clock base, the normal tones will be much lower, just as they were higher with bits 6 and 5 set. Go back to the 16-bit resolution music program and change line 110 to the following:
110 POKE 53768,17
By adding one, you change the main clock base as described above. Now RUN the program. At the end of the loop, the frequency is approxiniately 250 milliHertz, or one pop every four seconds!
Clearly, there is tremendous potential for experimentation with all these registers and options. As a bonus program, I have included a short sound-editor as Listing 3 that will let you change the frequency, distortion, and volume values for all four voices. The setting of each AUDCTL bit is displayed and alterable.
Once you have typed in the program and corrected any typos, RUN it. The screen turns orange for a few seconds and then displays a Graphics Mode 0 screen divided into six colored windows (the order of colors may vary slightly the next time you RUN the program).
The top window displays the title block, general status line, and command-reminder line. A"VOICE" indicates which voice you are working on at present, and the two numbers to the right of it are the values of the joined voices. These values are only updated when the appropriate AUDCTL bits are set, so you won't see them change at first. The last line of this window reminds you that the [STAR T], [SELECT], and [OPTION] keys, the stick and trigger are the only inputs used to control the program.
The next four windows display all information for each of the four voices: frequency, volume, and distortion on the first line, and a graphic representation of the frequency on the next three lines.
The last window shows the status of the AUDCTL register, including the value of each bit and the total decimal value (useful for writing POKE commands in your own programs). Using Table 3 in conjunction with this window permits you to selectively enable the various options.
At the bteginning of the program, the arrow on the graph for the frequency of voice number one is white and the other arrows are black. This is a reminder that you are working with voice number one now. Plug a joystick into Port One and move it left or right to increase or decrease the frequency value, and push the trigger to increase the frequency value by 25. Select another voice by moving the stick up or down, lighting up a different arrow.
By pushing the [SELECT] ann [OPTION] console buttons, you can increase the volume and distortion ofthe chosen voice. When they reach the maximum value, they "roll over" to zero.
The [START] button is used to change bits ofthe AUDCTL register. The voice's arrow disappears, and a box appears around bit 7 in the bottom window. Move the stick left or right to place the box over a different bit number. Press the trigger to change the value of that bit from zero to one or vice-versa. To go back and edit the voice again, press [START]. That's all there is to it. When you produce an interesting sound, write down the values and use them in your own program.
While the two utilities I have presented are quite useful, there is always plenty of room for improvement. If you come up with an interesting modification to any of the listings, send it to me c/o ANTIC so other readers can take advantage of it in their own programming.
Some suggestions: Modify the VBI routine to allow variable volume and distortion values for each note, handle songs longer than 127 notes, and use 16-bit resolution music. For you BASIC programmers, how about putting an option into the sound editor to display the meanings of the AUDCTL option and adding a sound storage feature (to disk or tape).
TABLE ONE: Sound Registers on POKEY
ADDRESS
Decimal | Hexadecimal | Label | Function |
---|---|---|---|
53760 | $D200 | AUDF1 | Voice #1 frequency (pitch) |
53761 | $D201 | AUDC1 | Voice #1 control (distortion/volume) |
53762 | $D202 | AUDF2 | Voice #2 frequency |
53763 | $D203 | AUDC2 | Voice #2 control |
53764 | $D204 | AUDF3 | Voice #3 frequency |
53765 | $D205 | AUDC3 | Voice #3 control |
53766 | $D206 | AUDF4 | Voice #4 frequency |
53767 | $D207 | AUDC4 | Voice #4 control
|
TABLE TWO: VBI Music Routine -
Important Memory Locations
ADDRESS
Decimal: | Hexadecimal | Label | Function |
---|---|---|---|
1712 | $06B0 | NUMV | Number of voices to play |
1713-1714 | $06B1-06B2 | V0ADR | Address of Voice #1 music in lobyte/hibyte form |
1715-1716 | $06B3-06B4 | V1ADR | Address of voice #2 |
1717-1718 | $06B5-06B6 | V2ADR | Address of voice #3 |
1719-1720 | $06B7-06B8 | V3ADR | Address of voice #4 |
1721-1724 | $06B9-06BC | DUR1-4 | Duration of voices #1-4 in 60ths of a second |
1725-1728 | $06BD-06C0 | STATUS1-4 | Status of voices #1-4; |
0 = play voice | |||
1 = voice inactive | |||
1729-1732 | $06C1-06C4 | COUNT1-4 | Position in music string currently being accessed |
1733-1736 | $06C5-06C8 | PAUSE1-4 | Pause flag: |
3 pause in progress | |||
0 = playing note | |||
1737 | $06C9 | AUD | Value to be stored in the AUDCTL register (see text)
|
TABLE THREE: AUDCTL Register -- Bit Options
Decimal Bit
Value | Number | Function Enabled (if Bit Set to 1) |
---|---|---|
128 | 7 | Turn 17-bit poly-counter into 9-bit poly-counter |
064 | 6 | Clock channel 1 with 1.79 MHz |
032 | 5 | Clock channel 3 with 1.79 MHz |
016 | 4 | Join channels 1+2 for 16-bit resolution |
008 | 3 | Join channels 3+4 for 16-bit resolution |
004 | 2 | Insert high-pass filter into channel 1 (clocked by channel 3) |
002 | 1 | Insert high-pass filter into channel 2 (clocked by channel 4) |
001 | 0 | Change main clock base to 15 KHz (normally 64 KHz) |
TOTAL = Value to POKE into 53768 (or 1737 if you're using the VBI music routines)
Christopher Chabrisr is is a high school senior and Junior Programmer for an IBM installation company in New York. He is a candidate Chess Master ans has represented the U.S. in international Chess competition.
Listing1:SNDEDTR1.BAS Download
Listing2:SNDEDTR2.BAS Download
Listing3:SNDEDTR3.BAS Download