Atari Sound by Bill Williams

1. Pokey and the Black Holes

We live in an audible universe. Though Homo sapiens's primary sensory orientation to the physicaI world is visual, the species would not have lasted long without the ability to hear a twig suddenly snap, and it’s unlikely it would ever have come to much if it had to negotiate treaties by mail. The dogfights in Star Wars wouldn't have kept you awake for long if they had really been fought in noiseless space. Nor will you play a game on your personal computer very long if the sounds coming from the speaker are superfluous, irritating, and otherwise poor.


Sound is an essential ingredient. The correct blend of the visual and auditory makes good games good. If you are going to write a game for the personal computer, you should know what you want it to sound like and how to get it to sound like that.


Most computers require microprocessor time for sound generation, either through assembly language coding or a function call to some sound routine provided in the operating system. In both cases the problem is the same: the calculation or graphics routine must stop for the duration of the sound. Considering the time this adds to program execution, it's no wonder that the average program drops the user into a black hole whenever it gets busy.


The Atari Sound System. When Atari designed their home computer system, they didn't just drop a 6502 microprocessor into a box. They also created three support chips—Antic, Ctia, and Pokey—to share the functions usually provided by the 6502 alone. The versatility built into these chips is tremendous; they have horizontal and vertical fine scrolling, color-luminance control, sound generation, player/missile graphics movement and collision detection, and a host of other miscellaneous I/O operations. In each of these, the supporting chips receive their instructions from the 6502, and then carry them out while the 6502 moves on to more esoteric tasks.


Sound generation is a good example of the power inherent in this design. Pokey handles four independently programmable sound channels, each with its own frequency, volume, and tone registers. Programs may pass the correct parameter to these registers, and the hardware continues to produce the requested sound until the program gives it different instructions. Advanced programming techniques can extend the range of tones available, provide higher frequency resolution, insert high-pass filters into channels 1 and 2 and bypass the tone generators entirely to create custom-designed waveforms.


Many applications, however, can be adequately handled by the Atari Basic interpreter.


The Sound Command. Atari Basic communicates to Pokey via the command Sound C,P,D,V where C,P,D, and V are expressions for the following parameters:


C: Sound channel. Voices are numbered from 0 to 3.


P: Pitch. 0 is the highest frequency, 255 the lowest. Expressions may be higher than 255, but the pitch will "wrap around."


D: Distortion. Can be any even number between 0 and 14. Ten is a pure square wave, and the other numbers will mix the pitch information with various amounts of noise. Motors, explosions, and the sound of surf can easily be found by experimentation with the distortion and pitch parameters.


V: Volume. The loudest is 15; 0 turns the channel off. This should not be thought of as simply an alternative to the volume knob on your television set—dynamic control of a sound can often yield radically different sounds from the same waveform.


Two things should be apparent immediately about this command: there is no duration parameter, and it can only refer to a single channel at a time. Having a duration parameter would require the processor to stop and count for the duration of the sound, which is what we're trying to eliminate. The channel selection parameter is a definite plus; with it, we can have four completely different sounds at the same time, making the Atari a true polyphonic instrument.


Filling the Void. The task for program 1 is to initialize the popularity of three candidates in a presidential election simulation and provide a subroutine that would "poll" the states and furnish the average popularity for each candidate. This subroutine would be called whenever the candidate made a significant change in his platform or in his campaign strategy.


The sound made during initialization wasn't critical; it was provided to confirm that the program was indeed alive and running. The subroutine, however, would be used constantly during the course of the simulation. Any sound provided would have to fit into the texture of the program. It would have to have an obvious reason for its existence, enhance the atmosphere of the game, and perhaps build a little suspense during the unavoidable pause necessitated by the tabulation of data.


For the initialization sound, the loop variables Scan and Idate were pressed into service for the pitch information. Multiplying the two together produces a series of descending tones. It doesn't take very long for the user to get a feel for the sequence and anticipate how long the calculations will last. In this example, of course, the database is small and the manipulations minimal. But when the loop gets complex, it becomes vital to give the user a way of approximating the time he will have to wait. This might be compared to a long drive to a friend's house. Most people find that as they become familiar with the route the trip seems much shorter than the first time they took it. The knowledge and anticipation of the time required greatly shortens the subjective time to the traveler.


In the averaging subroutine, data is displayed as incoming results from a nationwide poll. While the candidates huddle around the monitor, hyperactive Morse code dits and dahs away, occasionally spitting out a percentage to the cheers and groans of the participants. The tedious pause has been transformed into the excitement of election night, and involvement in the simulation has been heightened by three lines of coding: one line to randomly turn on the tone, one line to randomly turn it off, and another to stop the sound at the end of the routine.


It could be argued that the time spent selecting sounds could be better used for tabulation, thus speeding the entire process. This is true when the routine is so small that a large percentage of the coding is devoted to sound generation, as in our example. No real simulation, however, is going to be this simple, and as the length of the program increases, the audio portion's contribution to run time will be minimized, and the payoff for filling the void will become greater.


The Least Common Denominator. Most programs now being marketed are written for several major brands of microcomputers. Although this situation is better than the extreme exclusivity of earlier days, the sad thing is that there are very few programmers who go beyond the bare minimum of translating their program's syntax to each manufacturer's version of the language. The end result is a least common denominator program: it can run on all the available machines but uses the special features of none of them.


This is especially unfortunate for the consumer, who, by investing a considerable amount in his computer, has obviously evinced an interest in the special features of that brand. If the consumer then buys a program that uses none of these features, he will feel disappointment-if not anger.


None of this is necessary, and it is an inhibiting influence on the market. When an Atari owner buys a game from Doohickey Software and discovers that they developed their original programs on a TRS-80, he will probably stop buying programs from them. He wants a program that uses all 128 colors and stunning polyphonic sound effects; it's up to the programmer to provide them.


Next time, we'll look at music on the Atari, and examine a nifty program that simultaneously draws pretty graphics and plays four-voice randomly generated music. Until then, keep experimenting!


10 REM **********************************
20 REM *       SAMPLE PROGRAM ONE       *
30 REM **********************************
40 REM   
50 DIM STATES(50,3),CAND(3)
60 REM
70 REM **INITIALIZE POPULARITY**
80 REM
90 FOR IDATE=1 TO 3
100 CAND(IDATE)=0
110 FOR SCAN=1 TO 50
120 STATES(SCAN,IDATE)=INT(RND(0)*30)
130 REM "MAKE A NOISE**
140 SOUND 0,SCAN*IDATE,10,6
150 NEXT SCAN
160 NEXT IDATE
170 REM **SHUT OFF SOUND**
180 SOUND 0,0,0,0
190 REM "CALL AVERAGING ROUTINE**
200 GOSUB 1000
210 REM **INITIALIZATION COMPLETE**
220 REM MAIN BODY OF PROGRAM WOULD
230 REM FOLLOW....
240 END
250 REM
260 REM
1000 REM *** AVERAGING ROUTINE ***
1010 REM
1020 GRAPHICS 0
1030 PRINT "Only 3 weeks 'til the election!"
1040 PRINT
1050 PRINT "Latest UPI Poll Results follow...."
1060 PRINT:PRINT
1070 FOR IDATE=1 TO 3
1080 PRINT "Candidate ";IDATE;": %";
1090 FOR SCAN=1 TO 50
1100 CAND(IDATE)=CAND(IDATE)+STATES(SCAN,IDATE)
1110 REM
1120 REM *** SOUND PORTION ***
1130 REM RANDOM ON/OFF OF PURE TONE
1140 REM
1150 REM IF TRUE THEN SOUND ON
1160 IF RND(0)<0.4 THEN SOUND 0,20,10,8
1170 REM IF TRUE THEN SOUND OFF
1180 IF RND(0)<0.5 THEN SOUND 0,0,0,0
1190 NEXT SCAN
1200 REM **OUTPUT AVERAGE**
1210 CAND(IDATE)=INT(CAND(IDATE)/5)/10
1220 PRINT CAND(IDATE)
1230 NEXT IDATE
1240 REM **SHUT OFF SOUND**
1250 SOUND 0,0,0,0
1260 RETURN

2. Random Composition and Musical Indirection

Music plays an important role in our lives. We encounter it at work and at play, traveling down the road—even the telephone hold button plays us music. It should come as no surprise, then, that when presented with a computer equipped with sound generators, the first question most people ask is, "Can it play music?".


Notes on the Atari. Creating musical notes with the Atari is pretty easy. A table of pitch values for the notes is provided in the Atari Basic manual and in the Technical User's Notes. To play middle C, we look it up in the table and find the value 121. Placing this in the audio frequency register of a given channel will produce a middle C, provided we're using the "pure tone" distortion parameter, 10. (Why this won't work with other tones is beyond the scope of this discussion; we'll deal with it at a later date.) If you're in Basic, the command:


SOUND 0,121,10,8


will do the job.


Playing chords is a simple matter of looking up each note in the chord and plugging the corresponding value into a different sound channel. With four channels, you can form many complex chords (Cm9, F#maj7, and so on) or stick to a melody line with simpler voicings. To give the music rhythmic structure, preprogrammed music can be stored in a data table with duration values; the program, in turn, uses the duration value as a counter in a delay loop before playing the next note.


Random Composition. Producing random music is a little harder. There is a wealth of pitch values that don't fit into the equally tempered scale; in fact, if we just load random data into the frequency registers, the chances are roughly seven out of eight that we'll get one of these in-between pitches. The resulting noise will drive any person mad in the space of a few minutes. The problem is compounded by the absence of any obvious correlation between the musical intervals and their numerical equivalents. A half step at the bottom of the Atari's range is thirteen; a half step at the top is two. No workable formula is going to translate from musical intervals to pitch values.


The obvious solution is to store only "correct" values into a table and then randomly select a pointer to retrieve one of those values. This works, but as a stand-alone composer it has all the musical validity of a gorilla bashing away at a Wurlitzer. The notes are equally tempered, yes, but only the most avant-garde would call it music. Music distinguishes itself from random noise by having a format, something for the mind to catch and interpret. Music written within a format becomes a mix of expectation and surprise, and the craft of the composer lies in the creative way these two elements are combined.


A common format found in music is that of "key." Though there are twelve notes between octaves, many combinations of these notes will be nonsonorous, or harsh, to the ear. Nonsonorities can be used effectively, but care must be taken to use them skillfully, much like a good cook uses his hottest spices: sparingly, and always with a reason. By limiting the possibilities of note combinations before us, scales help us organize the tones into more sonorous sequences, and thereby increase our control over what nonsonorities we choose to include.


One of the most used scales in Western music is the major scale. It follows the pattern (given in terms of "steps"; for example, C to C# is a half step, C to D is a whole step):


1,1,1/2,1,1,1,1/2


Note that the pattern is not symmetrical, which will give the random composer fits in much the same way the nonpredictable pitch values did. The situation becomes really muddled if we want the composer to do something sexy like switch keys or correctly form and resolve common chords within the scale. Some sort of software device is needed to keep all these numbers organized.


Indirection. Atari owners should be familiar with the concept of indirection; the color registers are a method of indirectly specifying color. Rather than plotting the number of a color, the Atari plots the name of a color register that specifies the color. For example, putting a two into the screen display area does not mean display color 2, it means display whatever color is in color register 2. If color register 2 contains fifteen, then color 15 will be displayed. This is an extremely powerful graphics tool, for large amounts of previously plotted points can change color instantly with the movement of a single byte.


Indirection is an important concept because it lets us operate on data in terms of categories. We manipulate classes of data grouped together by their similarities, and in so doing we ignore the trivial individual characteristics found inside a class. To return to the color register example, we operate on data in terms of their similarity—they are all specified by register 2—and ignore their individual characteristics, the X and Y coordinates of each point.


What does this have to do with random music composition? Indirection, by letting us operate on similarity, allows us to concentrate on function. The notes in a scale, the chords formed in the series, all have specific functions depending on their position in the key. If the random composer is to resolve tones correctly, it must recognize and manipulate notes according to their functions in the scale.


For example, a crucial tone in the scale is the leading tone, the last note in the scale. The leading tone has a strong compulsion to lead into the note above it, the tonic (the first note in the scale). When we follow the leading tone with the tonic, we say we have resolved the tone: we have provided it with a satisfactory conclusion.


In the key of C, the leading tone is B, which means it will have a strong compulsion to resolve to C. In the key of E, however, B is not the leading tone: it is the fifth tone in the E major scale, not the seventh, and has no compulsion to move at all. We have, then, two entirely different functions for the same note, depending on its relationship to the key.


If a random composer is going to produce music with any intelligence, it must recognize the note not according to pitch, which is actually a trivial characteristic, but according to its function in the key. This means we have to deal indirectly with the note; specify it according to its relationship, and let some other software mechanism take care of the trivial pitch specifications.


Pitching Tonic. In our sample program, notes are specified by their relationship to the current key. This number will run from zero to six, with seven being the tonic an octave higher. The number is then converted into an offset by reading data from the table at 1100-1107. These data statements convert the sequential note specifications into the correct interval pattern for a major scale.


Before playing a note, the program adds the offset thus obtained to tonic, which specifies the key. This number is the real number of the note to be played, and is converted to a pitch value by reading the array pitch, then output to the sound generators. All this footwork enables us to specify notes in any key with the numbers zero through six. In our leading tone example, we would test for tone 6; if true, the note would be resolved to a 7, or alternatively, 0 (the same note an octave lower). This decision is made irrespective of the key being played.


The same idea is applied to the chordal accompaniment. Chords are specified by their roots' relationship to the key, and the following chord is dictated by the rules of resolution set up in the program. For instance, whenever a IV chord is played, there is a 50 percent chance that the key will be changed (lines 1490 through 1510). The new key selected will always be a fifth below the chord (line 1510; we add ten chromatic steps).


The Listing. Initialization is located at the end of the program, at lines 1400 through 2020. This was done to speed up execution of the main program loop at lines 100 through 890.


The pitch translation table is read into array pitch at line 1420. This line is skipped when initialization is reentered.


Lines 1450 through 1540 write a chord pattern for the piece based on the function of the chord in the previous measure. Add your own rules for resolving chords here.


Lines 1550 through 1590 create an eight-note pattern that is used to give structure to the melody. Lines 1600 through 2020 set up the graphics routine. This routine draws a line from a moving point to a stationary centerpoint in the upper left quarter of the screen, and then mirrors the pattern into the remaining quarters of the screen using the popular kaleidoscope algorithm. No claims to originality are made here, but the pattern produced is pleasing and is a good demonstration of the Atari's ability to maintain graphics and sound at the same time.


Lines 110 through 160 play the chord pattern. Note that the first three entries of the data table at 1100 through 1107 are borrowed to get the offsets from the root necessary to form the chord (the root, a third above, and a fifth above). This double use of the table occurs because the numbers are coincidentally the same.


Lines 170 through 380 play the melody. This melody, unlike the chord pattern, is not figured out ahead of time. Rules for tone resolution are in lines 190 through 340. If the rules don't apply, a random note is selected from the scale, with a 50 percent probability of actually being a note in the chord (line 300).


Lines 400 through 480 take care of moving the graphics points and drawing lines. Because this routine's execution time is a function of the length of the lines being drawn, it also is an interesting tempo control for the music: when the display gets busier, the music plays faster.


When thirty-two bars have been played, the screen is cleared, a new chord pattern is written, and the program repeats. While the chord pattern is being written, line 1450 plays the scale of the last key played to keep the transition smooth.


This program is really a jumping-off point for playing with music theory. The rules defined are the bare minimum necessary to make sonorous music. The more rules you add, the more intelligently the program will compose, and the less random the results will seem. More importantly, creative effort in the definition of a music system will pay off in an increased understanding of music, texture, and style.


In the next article, we'll take a look at the internal details of Atari's sound generators and find out why the pitch tables only work with distortion parameter 10.


10 REM ********************************************
20 REM *        AN AUDIO-VISUAL MEDITATION        *
30 REM *             by Bill Williams             * 
40 REM ********************************************
50 DIM PITCH(36),PATTRN(31),TONIC(31),MOTIF(7)
60 FETCH =800
70 REM CALL INITIALIZATION
80 GOSUB 1420
90 REM "MAIN PROGRAM LOOP"
100 FOR BAR=0 TO 31
110 REM UPDATE CHORD
120 FOR VOICE=0 TO 2
130 RESTORE 1100+VOICE:READ OFFST
140 OFFST=OFFST+PATTRN(BAR)
150 GOSUB FETCH:SOUND VOICE,PITCH(TONIC(BAR)+FOUND),10,4
160 NEXT VOICE
170 REM NOW FOR THE MELODY
180 FOR BEAT=0 TO 3
190 IF MELODY<>11 THEN 220
200 IF RND(0)<0.7 THEN MELODY=12:GOTO 370
210 MELODY=0:GOTO 370
220 REM PLAY MOTIF?
230 IF THEMEF=0 OR RND(0)< 0.1 THEN 270
240 THEMEP=THEMEP+1:IF THEMEP > 7 THEN THEMEP=0:THEMEF=0
250 OFFST=PATTRN(BAR)+MOTIF(THEMEP)
260 GOSUB FETCH:MELODY=FOUND:GOTO 370
270 REM START MOTIF?
280 IF RND(0)<0.15 THEN THEMEF=1
290 IF MELODY=5 THEN MELODY=4:GOTO 370
300 IF RND(0)<0.5 THEN 350
310 REM PICK NOTE IN CHORD
320 RESTORE 1100+INT(RND(0)*3)
330 READ OFFST:OFFST=OFFST+PATTRN(BAR)
340 GOSUB FETCH:MELODY=FOUND:GOTO 370
350 REM ANY RANDOM NOTE
360 RESTORE 1100+INT(RND(0)*6):READ MELODY
370 REM **PLAY MELODY**
380 SOUND 3,PITCH(TONIC(BAR)+MELODY+12),10,6
390 REM ** GRAPHICS ROUTINE **
400 X=X+DX:IF X<0 THEN X=0:DX=-DX
410 IF X>159 THEN X=159:DX=-DX
420 Y=Y+DY:IF Y<0 THEN Y=95
430 IF Y>95 THEN Y=0
440 PLOT CX,CY:DRAWTO X,Y
450 PLOT 319-CX,CY:DRAWTO 319-X,Y
460 PLOT CX,191-CY:DRAWTO X,191-Y
470 PLOT 319-CX,191-CY:DRAWTO 319-X,191-Y
480 IF RND(0)<0.1 THEN CX=INT(RND(0)*160):CY=INT(RND(0)*96)
490 NEXT BEAT:NEXT BAR
500 GOSUB 1440:GOTO 100
800 REM FETCH SUBROUTINE
810 IF OFFST>6 THEN OFFST=OFFST-7
820 RESTORE 1100+OFFST:READ FOUND
830 RETURN
1000 REM PITCH VALUE TABLE
1010 DATA 243,230,217,204,193,182,173,162,153,144,136,128,121,114,108,102,96,91,85,81,76,72
1020 DATA 68,64,60,57,53,50,47,45,42,40,37,35,33,31,29
1100 DATA 0
1101 DATA 2
1102 DATA 4
1103 DATA 5
1104 DATA 7
1105 DATA 9
1106 DATA 11
1107 DATA 12
1400 REM ** INITIALIZATION **
1410 REM 1420 IS "COLDSTART"
1420 TONIC(31)=0:PATTRN(0)=0:FOR L=0 TO 36:READ A:PITCH(L)=A:NEXT L
1430 REM 1440 IS "WARMSTART"
1440 TONIC(0)=TONIC(31)
1450 FOR BAR=1 TO 31:RESTORE 1100+INT(BAR/4):READ A:SOUND 3,PITCH(A+TONIC(0)),10,4
1460 TONIC(BAR)=TONIC(BAR-1)
1470 IF PATTRN(BAR-1)=6 THEN PATTRN(BAR)=0:GOTO 1520
1480 IF PATTRN(BAR-1)=1 AND RND(0)<0.8 THEN PATTRN(BAR)=4:GOTO 1520
1490 IF PATTRN(BAR-1)<>3 OR RND(0)<0.5 THEN PATTRN(BAR)=INT(RND(0)*7):GOTO 1520
1500 REM CHANGE KEY
1510 PATTRN(BAR)=0:TONIC(BAR)=TONIC(BAR)+10:IF TONIC(BAR)>11 THEN TONIC(BAR)=TONIC(BAR)-12
1520 SOUND 3,PITCH(TONIC(0)+12),10,4
1530 IF PATTRN(BAR)>6 THEN PATTRN(BAR)=PATTRN(BAR)-7
1540 NEXT BAR	

3. Just like Clockwork

The Atari has very little similarity to the analog monsters we normally think of when we talk of electronic sound generation. When it came to sound, Atari did what you might expect a bunch of computer engineers to do: they did it digitally.


Divide-by-Who? If we think of sound as a series of pulses corning from the television speaker, the frequency, or pitch, of that sound will depend on how fast the pulses come: the frequency of their appearance. For this reason, we measure pitch in terms of hertz (abbreviated to Hz). One hertz is equal to one pulse each second. The "pop-pop-pop" of 1 Hz is a very low frequency sound, and we usually deal with much higher pitches. Middle A, for example (in Basic, sound 0,144,10,4), is 440 Hz. When the pulses come this fast, we no longer hear them individually, but blended together in a "note" that we can hum. To deal with higher frequencies, the terms kilohertz (1,000 pulses a second) and megahertz (1 million pulses a second) are often used. Kilohertz and megahertz are abbreviated to kHz and MHz, respectively.


To control the frequency of a sound, a counter, or clock, is needed to time out these pulses. Each of the Atari's four channels can be tied to one of three different clock speeds: 1.79 MHz, 64 kHz, and 15 kHz. These master clocks are used to time out the pulses to the television speaker, and in so doing, they control the range of frequencies obtainable from that channel.


A sound generator with a choice of three different pitches is not a great leap forward for mankind. Obviously, something more is needed to extract a greater wealth of frequencies from these three master clocks. This extra something is called a divide-by-N circuit, and though it’s simple in concept, it’s a really neat trick. This circuit waits until N pulses have entered it, and then it puts out one single pulse. If, for example, N is 7, then the circuit will output one pulse for every seven pulses it receives.


When the divide-by-N circuit is placed in line with one of the selectable clocks, the circuit will mask out some of the pulses coming from the master dock. Changing the size of N will change the number of pulses that are eliminated and hence change the pitch output of the television speaker. Voila! A pitch source.


From this you can see that there are two variables affecting the final pitch sent out to the real world: the master clocking frequency that is input to the divide-by-N circuit, and the divisor N. The Basic sound statement uses only the "normal" clocking frequency of 64 kHz, thus limiting the user to manipulating only the size of N.


There is one other factor affecting the range of frequencies available from Pokey: the size of N. If N is a single-byte number, the lowest note possible will be the master clock divided by 255. If N is a two-byte number, the lowest note possible will be the master clock divided by 65,535. The size of N is called the "frequency resolution" of the channel. In normal operation, each of the four channels has eight-bit frequency resolution. The Atari, however, does permit joining two channels together to get sixteen-bit frequency resolution.


Some Pokables. To play with the hidden sound modes, we have to make a foray into the interior of the Atari. The sound statement won't take us much farther, so we're going to have to poke the hardware registers ourselves. The following memory locations are pertinent to sound generation:


AUDF1=53760 AUDC3=53765


AUDC1=53761 AUDF4=53766


AUDF2=53762 AUDC4=53767


AUDC2=53763


AUDF3=53764 AUDCTL= 53768


AUDF1 through AUDF4 are the frequency registers for each channel. This is where the divisor N is obtained. To find the frequency of the sound selected by AUDF1-4, use the following formula (FIN is the frequency input to the divide-by-N circuit and corresponds to the master clock frequency. FOUT is the frequency output to the real world):


FOUT=FIN/(2*(AUDF+M))


M will have a value of 1, unless FIN = 1.79. In this case, M will equal 4 if AUDF is an eight-bit counter, or 7 if AUDF is a sixteen-bit counter.


The actual values for each of the three master clock frequencies (FIN) are:


1.79 MHz = 1,789,789 Hz


64 kHz = 63,921 Hz


15 kHz = 15,699 Hz


AUDC1 through AUDC4 are the audio control registers for each channel. These registers contain the volume and distortion parameters for each channel packed into one byte. The high nibble contains the distortion parameter and the lower nibble contains the volume.


AUDCTL is the audio mode control register, and is the "master switch" for how all the sound channels will behave. Each bit in AUDCTL has its own meaning, so AUDCTL is really a collection of eight switches. The actions associated with each bit are given below. To use this table with Basic, just select the options you want and add up the corresponding POKE: numbers, then poke the result into AUDCTL.


POKE:128. Changes the seventeen-bit polynomial random counter into a nine-bit counter.


POKE:64. Clocks channel 1 with 1.79 MHz, instead of 64 kHz.


POKE:32. Clocks channel 3 with 1.79 MHz, instead of 64 kHz.


POKE:16. Clocks channel 2 with channel 1, instead of master clock. This ties channels 1 and 2 together to form a sixteen-bit AUDF register. AUDF1 becomes the low-order byte and AUDF2 becomes the high-order byte. The sound that is controlled by these channels "comes out" of channel 2.


POKE:8. Clocks channel 4 with channel 3, instead of the master clock. This ties channels 3 and 4 together into a sixteen-bit AUDF register in the same way POKE:16 ties channels 1 and 2.


POKE:4. Inserts a Hi-Pass filter into channel l's output. The filters cutoff frequency is controlled by AUDF3.


POKE:2. Inserts a Hi-Pass filter into channel 2's output. The filter's cutoff frequency is controlled by AUDF4.


POKE:1, Changes the normal 64 kHz clocking frequency into a 15 kHz clock.


How Do I Use All This? First, initialize Pokey. Pokey handles all the I/O stuff, too, so it has to be set up for sound generation after every I/O operation (like, for instance, reading in your program). Executing a sound 0,0,0,0 at the beginning of your program will take care of this.


Next, select the sound mode options you want and poke the corresponding value into AUDCTL. After that, just poke the distortion and volume parameters into AUDC1-4, and your frequency parameters into AUDF1-4. If you use the sound statement again, however, it will wipe out any special AUDCTL modes you may have selected, so you'll have to repoke in your value after every Basic sound command.


To get you started, sample program 1 shows the procedure for setting up two channels of high frequency resolution sound clocked from 1.79 MHz. The program then plays a short musical sequence from the data statements at the end.


To play musical notes in these special sound modes, of course, you need a chart showing the correct values for each equally tempered note in the Western scale. The note chart included in the Basic manual will no longer work: it's for eight-bit frequency resolution and a 64kHz clocking rate. That's where sample program 2 comes in.


Sample program 2, when run, will print out a list of the musical notes and their corresponding two-byte values for each of the three clocking frequencies. Just look up the note you want and poke the first number into the high-order AUDF byte, the second number into the low-order AUDF byte.


The range of the charts goes beyond anything practical: the lowest C is 8.17 Hz, which hardly sounds musical, and the highest C is 134 kHz, which considerably outdistances your stereo's frequency reponse. You'll also notice that as each chart nears the top of its range, weird things start to happen: the value of 0,1 is listed for every note between G# and D#. This is because decimal numbers are not allowed in the AUDF registers.


This concludes our discussion on clocking. The sharp-eyed may have noticed that the distortion modes were glossed over; because of their nature, it is important to understand Pokey's clocking system first. Next time we'll delve into the topics of noise and waveform cancellation.


20 AUDCTL=53768
30 AUDF1=53760:AUDC1=53761
40 AUDF2=AUDF1+2
50 AUDC2=AUDC1+2:AUDC3=AUDC2+2:AUDC4=AUDC3+2
60 REM Initialize everything
70 SOUND 0,0,0,0
80 REM Poke POKEY
90 POKE AUDCTL,120
100 REM Shut off unused outputs
110 POKE AUDC1,0:POKE AUDC3,0
120 REM Turn on channels 2 and 4
130 POKE AUDC2,168:POKE AUDC4,168
140 RESTORE
150     FOR TONE=1 TO 12
160         FOR VOICE=0 TO 4 STEP 4
170             READ HIBYTE,LOBYTE
180             POKE AUDF1+VOICE,LOBYTE
190             POKE AUDF2+VOICE,HIBYTE
200         NEXT VOICE
210         FOR WAIT=1 TO 50
220         NEXT WAIT
230     NEXT TONE
240 GOTO 140
400 DATA 0,88,0,120,0,78,0,106,0,94,0,128,0,88,0,120,15,221
420 DATA 47,150,14,33,42,100,13,85,40,3,14,33,42,100,11,224
440 DATA 28,73,10,148,31,192,10,148,127,22,10,148,63,136	
Sample Program 1.
90 DIM CLOCK(2),M(2),NAME$(5)
110 CLOCK(0)=15699
120 CLOCK(1)=63921
130 CLOCK(2)=1789789
150 M(0)=1
160 M(1)=1
170 M(2)=7
190 FOR FREQ=0 TO 2
200 LPRINT: LPRINT
210 LPRINT "CLOCK: ";CLOCK(FREQ)
220 LPRINT
240 FOUT=8.1759375
260 SKEY=0
280     AUDF=INT((CLOCK(FREQ)/FOUT)-M(FREQ)+0.5)
290     REM Break into high and low
300     HIBYTE=INT(AUDF/256)
310     LOBYTE=AUDF-(HIBYTE*256)
330     IF HIBYTE>255 THEN 370
340         RESTORE 500+SKEY
350         READ NAME$
360         LPRINT NAME$;"= ", HIBYTE;".";LOBYTE, INT(FOUT*100)/100;"  Hz."
370         SKEY=SKEY+1
380         IF SKEY=12 THEN SKEY=0
390         FOUT= FOUT*1.059463094
410     IF HIBYTE>0 OR LOBYTE>0 THEN 280
420 NEXT FREQ
430 END
500 DATA C
501 DATA C#/Db
502 DATA D
503 DATA D#/Eb
504 DATA E
505 DATA F
506 DATA F#/Gb
507 DATA G
508 DATA G#/Ab
509 DATA A
510 DATA A#/Bb
511 DATA B
Sample Program 2.

4. A Different Shade of Noise

Let's discuss some terms that crop up in audio synthesis work and see how they relate to the Atari's distortion parameter. These terms are quite handy to have around when describing sounds. You might get strange looks when you suggest "using a little brown noise" to a fellow hacker, but it’s more dignified than just making gurgling noises.


Boom–Chicka–Boom. The word noise has a derogatory connotation to most people. It conjures up images of jackhammers, trash can lids, or your roommate's taste in music. Purge yourself of this notion! The soothing sounds of wind and surf, the pitter-patter of raindrops on a window, and the rustling of leaves in a summer breeze all fit into the category of noise, as do explosions, cymbals, and hand clapping.


What does noise mean, then? Well, if we considered the similarities in the examples just cited, we might conclude that noise means an unpitched sound. After all, none of the sounds we've mentioned so far produces a note that we could whistle. As a definition, this is not far off, but it's misleading.


Let's try an experiment.


The Noise Demo. This installment's a quick-and-dirty examination of the components of noise. What this program reveals may surprise you. It's written in assembler because Basic is too slow to get the point across. Listing 1 is the relocatable assembler code. Listing 2 is a Basic program that will poke the code into memory and execute it. To try this out, you'll need a set of paddles plugged into port 1.


Type in the Basic program and save it. A machine language program's greatest joy in life is to eat itself, and a single error will probably lock up your system. If it does and you've saved a copy, you can just turn the computer off, reload the program, and find your error. If you haven't saved your program, you get to type it in again (and run the risk of making another error).


Once you've saved the program, turn paddle 1 clockwise and run the program. You should hear a series of random notes; the audio control register has been poked with $A8, which is the Basic equivalent of distortion register 10 (pure tone), volume 8. Now turn the paddle counterclockwise, and the rate at which random notes occur will speed up.


So far, so good. But as you approach the CCW limit of the paddle's rotation, something strange happens to the tone. Yes, this is still distortion parameter 10!


Some Generalizations. From this little experiment, we can conclude that noise is not an unpitched sound, but a lot of random frequencies heard in quick succession. Too many notes clamoring for our attention, if you will. Our ears throw up their hands (lobes?) and can no longer distinguish the individual notes; hence our first conclusion that there was no "pitch" to the sound.


What, then, differentiates one noise type from another? The sound of the wind is quite obviously different from the sound of thunder, and yet we've just decided that both are made from the same stuff—random notes.


The answer lies in how those notes are distributed. We call this bandwidth. Say, for example, there is an equal probability of low frequencies occurring as of high frequencies. We would say that this noise has a wide bandwidth. If graphed on a frequency continuum, the sound would be scattered across a wide range of pitches. Audio engineers draw an analogy between this frequency spectrum and the light spectrum. This kind of sound is called white noise. White noise is what you hear when you turn your Atari off before turning the television set’s volume down.


                  ;NOISDEMO.ASM
                  ;(Relocatable code)
                  ;
                  ;Hardware equates
= 0270            PADDL0     =    $270
= 027C            PTRIG0     =    $27C
= D20A            RANDOM     =    $D20A
= D200            AUDF       =    $D200
= D201            AUDC       =    $D201
                  ;
                  ;
0000 68           INIT    PLA                ;pop the stack
0001 A9A8                 LDA    #$A8        ;start pure tone
0003 8D01D2               STA    AUDC
0006 AD0AD2       LOOP    LDA    RANDOM      ;get random freq
0009 8D00D2               STA    AUDF        ;store it
000C AE7002               LDX    PADDL0      ;get delay value
000F F0F5^0006            BEQ    LOOP        ;no delay
0011 88           DLAY    DEY                ;twiddle thumbs
0012 D0FD^0011            BNE    DLAY
0014 CA                   DEX
0015 D0FA^0011            BNE    DLAY
0017 AD7C02               LDA    PTRIG0      ;trigger up?
001A D0EA^0006            BNE LOOP           ;do it again?
001C 8D01D2               STA AUDC           ;shut up!
001F 60                   RTS                ;back to basics	


Listing 1.


10 REM Basic routine to install
20 REM and execute noise demo
30 DIM DEMO$(1),DEMO(6)
40 FOR LOC=ADR(DEMO$)+1 TO ADR(DEMO$)+32
50 READ BYTE
60 POKE LOC,BYTE
70 NEXT LOC
80 REM Call Demo-
90 REM Press paddle trigger to
100 REM return to Basic
110 Q=USR(ADR(DEMO$)+1)
120 END
2000 DATA 104,169,168,141,1,210,173,10,210,141,0,210,174,112,2
2010 DATA 240,245,136,208,253,202,208,250,173,124,2,208,234,141,1,210,96	

Listing 2.


The sounds made by wind and surf are generally called pink noises—the top end of their bandwidth is vacant and they only have pitches between the low frequencies and the midrange frequencies. Other terms often used are red noise, which refers to deep explosions and the like; and brown noise, which is the kind of sound that peels paper off of $200 speaker cones. All of these terms simply describe the frequency content of the noise. The lighter the color, the more high frequencies the noise has.


Is there a black noise? Well, if we follow out the light analogy, black noise would be silence. Try talking about that one and you'll get strange looks from anyone!


Randomness. So far we've looked at just one characteristic of noise—how widely the random frequencies are distributed. Another factor that affects the character of noise is the degree of randomness involved in selecting these frequencies. If this distinction seems trivial to you, consider the following patterns:


[A] 40 2 40 2 40 2
[B] 7 6 6 5 7 5 6


Pattern A has a wide bandwidth-38-and yet we can easily discern the pattern, Pattern B has a very narrow bandwidth-3-and yet there is no obvious pattern. Pattern B is more random.


Subjectively, the more random the noise is, the more "natural" it sounds. Repetitive patterns tend to sound machinelike. The random number generator used in the demo program is pretty random, so the pattern of the sound is not at all obvious to our ears.


To see how this applies to the Atari, we have to rephrase our question a bit. Rather than asking "How random are the frequencies?" let's ask, "How long does it take before the pattern repeats?" Computers, you see, are notoriously nonrandom. The same laws that assure us of the same answer to an identical question (A = 2 + 2) also keep us from getting a truly random number. So, if there's always going to be a pattern, the key question is, "How long is the pattern?" A pattern that takes a year to repeat is going to seem random to most of us humans; pattern A (used earlier) doesn't seem random at all.


Down from the Ivory Tower. We now have two characteristics of noise defined: bandwidth and randomness. Armed with these new distinctions, let's trudge back to the subject of clocks from last issue and see if we can make some sense out of all this.


In an earlier column, we detailed how a pitch is derived from the master clocks; in pure tone mode, these pulses are passed directly to the television set. With the other distortion modes, this derived waveform is passed on to other circuits called comparators. These circuits omit random pulses of the waveform and thus distort the waveform so that a single pitch is turned into a series of randomly varying pitches. This is how we get noise.


Remember the divide-by-N circuit? The principle is the same. A number of pulses enter the comparator, and some of them are masked out. With this circuit, however, the pulses that are removed are random and unpredictable; thus the waveform becomes unpredictable, too.


The key thing to be aware of is that the comparators don't add any pulses, they only subtract them. Therefore, the pitch can never be higher than the originally derived tone. The lower the original pitch is, the lower the maximum noise frequency will be. Thus, by lowering the frequency (putting, greater numbers in AUDF), we restrict the bandwidth of the noise.


This is called a low-pass filter, because, no matter what the corner frequency is (the original pitch selected by AUDF), the low frequencies will always be allowed to pass through. It is only the frequencies above the corner frequency that are cut off. Thus, as the corner frequency is lowered, we go from white noise to pink noise to red noise and so forth. In this way, the pitch parameter in these modes is similar to the tone control on your stereo.


The distortion parameter, on the other hand, controls the randomness of the Comparators. The comparators do what their name implies—they compare the input waveform to another input waveform and output only the similarities. If this other waveform (call it input B) is random, then the pulses allowed to pass will be random. By controlling the randomness of input B, then, we control the randomness of the output waveform.


How do we change the randomness of the input B waveform? The Atari generates this waveform by using something called a polynomial counter. Basically, a polynomial counter is a shift register that derives its first bit from the state of its other bits (the derivation is made by boolean logic gates). This bit is then shoved into the shift register, all the other bits move down to make room for it, and—presto!--a new random number. The next new bit is then derived from this number.


The upshot of this is that the more bits there are in the poly counter, the longer it takes for the pattern to repeat. The Atari has three different poly counters, all of different bit lengths-4, 5, and 17. The 17-bit poly takes a long time to repeat, while the 4bit counter repeats the most. The distortion parameter just shunts different combinations of these poly counters into the comparators, producing different degrees of randomness in the distortion pattern. From our previous discussion, we can predict that the 17-bit poly is going to sound much more natural than the others.


The following chart shows how the poly counters are combined for the different distortion parameters:


AUDC/poly counters used 0 = 5 and 17 bit
2,6 = 5 bit
4 = 5 and 4 bit
8 = 17 bit
10,14 = none
12 = 4 bit


All of the distortion values, by the way (even pure tone), lower the resultant pitch an octave before it reaches your television set.


You should now be able to see why the pitch-note tables only work with distortion parameter 10. The random masking of pulses produced by the other distortion modes makes it very difficult to predict what the subjective note is going to be. All sorts of wild things can happen with waveform cancellation—but that's another column.


Finally, a small sermon. The real object of this column is to make the beeps and squawks you get predicable, so that you will spend less time getting that perfect sound for your program. You now have a considerable amount of information under your belt. Use it! When you want a particular sound, find a sample and analyze it. What is its bandwidth; how repetitive is the distortion pattern? Ideally, you should be able to predict the "quality" of a sound before you even sit down at the keyboard. Sound impossible? Practice—you may be surprised!


Editorial Erratum. The people cried "Space! Space!" But there was no space. So we crunched the last twelve lines of November's sample program 2 into two lines, the realization of our tragic error coming too late. To get those well-tempered high-frequency notes printing like they ought to, replace the last two lines with these:


500 DATA C
501 DATA C#/Db
502 DATA D
503 DATA D#/Eb
504 DATA E
505 DATA F
506 DATA F#/Gb
507 DATA G
508 DATA G#/Ab
509 DATA A
510 DATA A#/Bb
511 DATA B	

5. Hallelujah - Chorusing for the Atari

Look at any modern synthesizer advertisement and you'll find the recurring use of an unlikely word: fat. "Nice fat sound!" they scream. "Multiple oscillators for that modern fat sound." Guitarists, too, have been hit with a blitz of effects devices promising "fat sounds." Members of the musical community have not lost their collective mind; they're just trying to describe the subjective effects of three different techniques: phasing, flanging, and chorusing. Our discussion of waveform cancellation, then, will conclude with advice on how to make your Atari gain weight.


Natural Chorusing. Not surprisingly, a good example of natural chorusing is a choir. The "bigness" of the choral sound cannot be attributed just to additional volume, or to the variety of voice qualities found among its individual members. Consider a string section made up of identical violins; there is still something that gives the sound a "bigness" or "fatness" that a single violin does not have.


The secret to this chorusing effect is disarmingly simple: no matter how good the vocalists or how accurate the violinists, they will always be slightly out of tune with each other. This slight deviation of frequencies, rather than being unpleasant, creates a shifting waveform that is inherently more interesting than the static tone produced by a single performer.


How Does It Work? To understand why this happens, you should understand that no matter how many sounds are in the air at one time there is only a single waveform present. If we play Mozart on a crowded city street and run the resulting mix of sounds into an oscilloscope, we will see one very complex waveform. This seems to run contrary to common sense, but consider a speaker cone or an eardrum. Both are membranes designed to interact with the air, and, like most objects, they can be in only one place at a time. Yet, a speaker can reproduce the sound of three people talking at once, and the ear can hear it. This is in spite of the fact that both the speaker cone and the eardrum can only jiggle back and forth in a single pattern at any given moment.


Obviously, multiple sounds must combine together to form a single pattern that can be reproduced by a single membrane. Furthermore, the combination of these waveforms must follow a rational, predictable rule—otherwise, our ears would not be able to decode this waveform back into its multiple parts. You may have already encountered this rule in trigonometry; it's called graphical addition.


The idea is fairly simple. Graph two waveforms on a chart. The X axis will represent time, and the Y axis will represent amplitude (the volume of the pressure wave). Now travel along the X axis, and at every point along that line add up the V values (amplitudes) of the waveforms. Plot the result, and you will see a third waveform appear. This waveform is the summation of the two original waves, and if both were heard simultaneously a graph of the resulting sound would look like the third wave, the wave we obtained by graphical addition.


For example, suppose that, at 2 seconds, waveform A has an amplitude of 4 and waveform B has an amplitude of 3. The resulting waveform will have an amplitude of 7 at that point. Because sound waves are often graphed symmetrically balanced about the X axis, we will also get negative numbers. The method of graphical addition is the same. If, at 7 seconds, waveform A has an amplitude of 6 and waveform B has an amplitude of —4, the resulting amplitude will be 2.


The key point to realize in all this is that sound waves affect each other. We tend to visualize separate sound waves hanging blissfully in the air, totally unconcerned with each other. Nothing could be further from the truth. When a flute plays a duet with a piccolo, the sounds the instruments make interfere with one another and produce a completely different wave shape.


The ramifications of this are important. Take, for example, two sine waves of the same frequency that are 180 degrees out of phase; this means that their shapes are identical, but that, when wave A is at its top, wave B is at its bottom. At any point you choose along the X axis, the sum of the two waves will be the same: if they're symmetrically balanced about the X axis, their summation will be a straight line at zero. Because the ear only responds to change, we won't hear a thing.


The effect we're most interested in, though, is illustrated by sample program 1. Type it in and run it. You will see two sine waves drawn at the top. They are very close in frequency but not identical. The bottom wave is the summation of these two and shows an interesting pulse in the amplitude of the waveform.


When the graphs are completed, the program will start two tones very close together so that you can hear the audio equivalent of this effect.


Practical Applications. One use for this effect is to thicken up musical lines. It's a sad fact of life that single oscillators tend to sound wimpy, so many commercial programs now use this effect for their theme musk. A lot of users wonder what special "trick" was used to soup up the sound. The answer is disappointingly simple.


10 REM Demo 1: Waveform Cancellation
20 GRAPHICS 8:SETCOLOR 2,0,0:SETCOLOR 1,0,12:COLOR 1
30 DIM A$(1)
40 FOR Tni0 TO 31 STEP 0.1
50 A=10*SIN(2•T)
60 B=10*SIN(2.2*T)
70 PLOT T*10,A +20
80 PLOT T*10,B+ 50
90 PLOT T*10,A+B+100
100 NEXT T
110 SOUND 0,200,10,8:SOUND 1,201,10,8
120 PRINT "Press return to stop";
130 INPUT AS	

Sample Program 1.


Sample program 2 demonstrates this technique with a tune we all know and love. If you select the no-chorusing option, the tune is played with a single generator. Selecting chorusing causes the frequency specification to be incremented by one and output to a second generator. Of course, one drawback to this method is that it limits the number of independent voices you can use.


Do not, however, be trapped into thinking of this as only a musical device. Try this technique with other distortion specs and different frequency ranges. Many effects, from hollow booms to Defender-like growls, can be obtained this way. Fattening up your Atari can add a lot of impact to your program!


Next issue: Enter the assembler.


5 REM Demo 2: Chorused Melody 
10 DIM WORD$(11) 
20 ? "Do you want chorusing (0=no,1=yes)" 
30 INPUT CHORUS 
40 GRAPHICS 2:POSITION 0,4 
50 RESTORE 
60 FOR L=1 TO 8 
70 READ FREQ,WORD$ 
80 ? t16;WORD$; 
90 FOR V=14 TO 0 STEP —2 
100 SOUND 0, FREQ,10,V 
110 IF CHORUS THEN SOUND 1,FREQ+1,10,V:GOTO 140 
120 REM This just equalizes the tempo 
130 FOR D=1 TO 3:NEXT D 
140 NEXT V 
150 NEXT L 
160 GOTO 20 
170 DATA 96,HAVE ,96,YOU ,96 
180 REM Note 6 spaces after "played" 
190 DATA PLAYED ,96,A,81,TAR 
200 DATA 85,1 ,96,1-0,108,DAY?	

Sample Program 2.

6. A Change of Tone

This column's title has a double meaning. The original meaning has to do with the topic at hand: custom waveforms on the Atari. There has been a lot of interest in the subject lately, and with good reason. Custom waveshaping can produce realistic sound effects, good instrumental imitations, and probably one of the hottest fads in the industry right now voice synthesis. It's a ripe topic for a series on sound generation.


All these techniques, however, depend heavily on assembly language. Higher-level languages are not efficient enough to drive the speaker at the needed frequendes. Furthermore, as we continue to expand the range of sounds available to us, we're going to keep running into this situation. Hence the second meaning of the title: The tone of this column is going to take a serious bend toward assembly language.


This should not be a cause for panic amongst Basic programmers. Every effort will be made to provide hybrid code that will be useful or interesting to you. This month is a case in point: The Basic listing provides a convenient method for producing sine waves that could easily be adapted for different purposes. If you are familiar with assembly, of course, you will be able to experiment more fully with custom waveforms.


Thus ends the editorial. Let's talk about sound.


From our previous discussion of waveform cancellation, you should be getting a feel for what a sound wave is: a rhythmic series of pulses in the air. The shape of each pulse is what we call the waveform, and it determines the character of the sound. To make a sweeping generalization, waveshapes with sharp edges sound buzzier, or harsher, than waveforms with rounded edges. Anyone interested in obtaining a wider range of tones from the Atari, then, should take an interest in custom waveform generation, for it offers a greater control over the character of a sound.


If you are having trouble visualizing just what a waveform does, let's review with an example we all know and love, the Atari's "pure tone distortion parameter 10. The normal waveform output by this setting is a square wave, a simple up/down, on/off shape that is perfectly suited to digital electronics. When translated to the speaker, a square wave means (1) Start fully retracted; (2) instantaneously become fully extended; (3) instantaneously become fully retracted again.


Which, incidentally, is why you don't really hear a true square wave. Matter transmission has not yet been invented, and the speaker cannot truly jump from one point in space to another. It does, however, move there as fast as it can, and the sound is about the same.


The key point to realize is that a waveform is a set of "instructions' to the speaker cone having to do with its pattern of movement. When we output a waveform to the speaker, it dances back and forth in time to the waveshape we give it. This vibrating paper cone then pushes the air, which in turn pushes a membrane in our ear. The whole point of sound generation, then, is to deliver an interesting pattern of back/forth movement to our eardrum. Fortunately, a speaker has more than two positions it can occupy, so it can reproduce zillions of patterns besides on/off/on/off. Let's say that our speaker has sixteen different positions, with 0 being fully retracted and 15 fully extended. Producing different waveshapes would involve sending out a repetitive series of numbers between 0 and 15. The pattern of numbers would determine the waveshape and hence the character of the tone. The pitch, or frequency, of the sound would depend on how quickly the sequence repeated itself. Faster sequences occur at a higher frequency and thus have a higher pitch.


As you might now guess, the Atari provides us with a way to communicate directly with the speaker cone and deliver our on pattern of "instructions/' or our own waveshape. By setting bit 4 in AUDCX, we enable a special mode called forced output, which passes the volume parameter directly to the speaker in the form of a fixed voltage. In this way we can make the speaker dance in any pattern we want (within some very definite limits).


if you don't remember AUDCX, it's the audio control register for each voice, referred to as AUDC1 (address $D201), AUDC2 ($0203), and so on, The top nibble of AUDCX contains the distortion parameter for the channel, and the bottom nibble contains the volume information. A pure tone of volume 8, for example, is $A8, the $A (decimal 10) for pure tone, the 8 for volume. Implementing forced output simply involves storing a value between $10 and $1 F in a channel's AUDC register. A value of $10 retracts the speaker, Vi F fully extends the speaker, and values between these extremes put the speaker in a corresponding middle position.


For example, successively storing the values $10,$1F,$10,$1F... in AUDC1 will produce the square wave normally produced by the pure tone setting in channel 1. (Except it will be a result of your own labor, and will therefore sound much better!) A more interesting sequence might be


$10, $14, $18, $1C, $1F, $10, $14, $18, $1F...


which would produce a ramp wave.


There are, however, some drawbacks to forced output that you should be aware of. The first one is a biggie: processor time. To produce a high frequency, the 6502 must devote all of its resources to the production of the waveform. This means your program will grind to a halt every time you want it to sing. No graphics changes, nothing. That isn't the whole story, though. To produce an undistorted waveform, all interrupts must be disabled, as must all DMA.


if you're not familiar with these terms, they refer to events that momentarily interrupt the 6502 from running your program. Whenever a key is pressed, a keyboard interrupt occurs, and the 6502 saves all the information it was working with, jumps to a program to handle the interruption, and then returns to your program after recovering the information it saved. It's kind of like reading a book during a conversation: You're always putting a finger on the page so you can answer a question. You don't get a lot of reading done this way, and the 6502 doesn't get a lot of computing done. DMA stands for direct memory access, and it happens whenever the screen display is generated. ANTIC (the chip that handles the display) stops the 6502 so it can borrow the address and data lines. These constant interruptions create buzzing "gaps" in the waveform that must be stopped.


What does this mean to the user? Well, ugliness. Whenever the sound routine is entered, the display flashes off and is replaced by the background color. If the background color is black, it looks remarkably like a system crash. Also, if the routine is entered and exited repeatedly, like the demo, the flashing will drive all but a blind man to distraction.


What can be done about It? Precious little. Storing the normal screen color (in graphics 0, $94) in the background register helps. This means that just the text flashes on and off. Yes, it's still ugly.


There are other limitations, too. Memory usage is one of them. Voice synthesis was mentioned earlier as a spin-off of forced output mode; the idea of voice synthesis without having to buy any additional peripherals is indeed exciting, and there are some programs on the market now that do exactly that. Unfortunately, the data storage requirements for even a simple sentence are staggering, and the serious programmer must carefully consider what options will be given up for the added sexiness of voice output. A lot of machine code can fit into the space reserved for "We got you, Earth slime!" This is not to say that voice synthesis has no place in the Atari, just that some serious soul-searching should be conducted before the decision is made.


The Demo. The demo shows a more modest use of forced output: It provides a way to play custom waveforms from Basic. As written, the demo plays sine waves, which are considerably more soothing than square waves and should be a welcome change of tone for the nonprogrammers in your house.


The assembler listing is fully commented and should be easy to follow. It accepts a waveform table length, note duration, and frequency from Basic. The wave table is fixed on page six, and can be a maximum of 256 steps long. The duration and frequency can likewise he any number between 0 and 255.


;***********************
;*  Forced Output Demo *
;*  Assembler Listing  *
;***********************

NMIEN   = $D40E
IRQEN   = $D20E
DMACTL  = $D400
AUDC    = $D201
AUDCTL  = $D208
SKCTL   = $D20F
COLBK   = $D01A
DUMMY   = $D01E
;
WVTAB   = $600
;
      ORG $CB
WVTL  DS  1
DURA  DS  2
WVPNT DS  1
WFREQ DS  1
WCNT  DS  1
;
      ORG $4000 ;(relocatable)
;
PLA;number of args
PLA;wave table length hi
PLA;wave table length In
STA WVTL
PLA;duration hi
PLA;duration lo
STA DURA+1
PLA;freq hi
PLA;freq lo
STA WFREQ;store freq
STA WCNT
LDA #0;init pointer
STA WVPNT
;no interrupts, no DMA
STA NMIEN
STA IRQEN
STA DMACTL
;init audio
STA AUDCTL
STA DURA
LDA #3
STA SKCTL
;color the screen
LDA #$94
STA COLBK
;
PLAYIT DEC WCNT;dec freq cnt
BEQ DOVOX
;waste some machine cycles
    LDX    #5
WASTE DEX
BNE WASTE
STA DUMMY,X
BEQ UPDUR
DOVOX LDA WFREQ ;recharge cnt
STA WCNT
;get index into wave table
LDY WVPNT
;get volume and set force output
LDA WVTAB,Y
ORA #$10
;punch the speaker
STA AUDC
;move the wave pointer
INY
;check for table end
CPY WVTL
BCC NOWRAP
;wrap pointer
LDY #0
NOWRAP STY    WVPNT
;update duration counter
UPDUR DEC DURA
BNE PLAYIT
DEC DURA+1
BNE PLAYIT
;time's up, return to Basic
LDA #$FF
STA NMIEN
STA    IRQEN
RTS
;
END	

No real tricks are used in the code, but notice the placing of the delay mechanism: It is important to place the delay in between each step of the waveform, so that the waveshape is preserved. Also note that if the time has not yet been reached to output another wave step, a small delay is entered to equalize the run time of the loop.


There are two distorting factors: Every 256 iterations, an additional seven machine cycles are used to update the high byte of the duration count, and every time the waveform is repeated, an additional two cycles are used. Purists may cringe, but when the 6502 is clicking along at 1.79 MHz, the lost cycles are minimal.


Note, too, that the assembler portion forces AUDCX bit 4 set to 1, so that Basic can just pass the volume bits through the waveform table. Setting a specific bit is a lot easier in assembler than in Basic.


The Basic routine first pokes the machine code into place (lines 29 through 50), then compiles the waveform table. The formula used will form a sine wave composed of LNG steps. The value of LNG may be changed (line 9) to any value up to 255. The larger the waveform table, the greater will be the resolution of the sine wave, and thus it will more accurately depict a true sine wave (in more subjective terms, it will sound mellower"). As the waveform gets longer, however, the highest frequency decreases, because there are more steps to cover before the shape repeats. Sine waves sound best at higher frequencies, and so long wavelengths are not very useful with this formula. Other waveforms work a lot nicer in the lower frequencies—try the ramp waveform discussed earlier.


After poking in the waveform, Basic reads off a frequency from the data table at lines 26 and 27, and calls the machine language speaker driver.


The demo is easily modifiable for different waveforms; just poke your desired sequence from locations 1536 to 1791. Waveshapes could be read in from a data table or entered in with any bizarre editing scheme you might think of. There is a lot of potential for experimentation here, even without digging into the assembler portion.


Oh yes; always remember to save hybrid code before you run it. A single error in the machine code data statements will very likely eat up the program, the operating system, DOS, your cat....


0001 REM *************************
0002 REM *  FORCED OUTPUT DEMO   *
0003 REM *   by Bill Williams    *
0004 REM *     Basic Listing     *
0005 REM *************************
0006
0007 GOSUB 0029
0008 SETCOLOR 4,9,4
0009 LNG=10:TEMPO=10
0010 ? "Waveshape:"
0011 FOR L=0 TO LNG-1
0012    N=INT(SIN(L*6.2831853/LNG)*7)+7
0013    POKE 1536+L,N
0014    ? N;" ";
0015 NEXT L
0016 FOR REPEAT=1 TO 4
0017    RESTORE 0025
0018    FOR MELODY=1 TO 14
0019    READ N
0020    USR(ADR(QQQ$)+1,LNG,TEMPO,N)
0021    NEXT MELODY
0022 NEXT REPEAT
0023 END
0024
0025 REM Melody notes
0026    DATA 2,16,8,4,2,4,2,16
0027    DATA 20,16,20,18,16,15
0028
0029 REM Machine code (double-check!)
0030    DATA 104,104,104,133,203,104,104
0031    DATA 133,205,104,104,133,207,133
0032    DATA 208,169,0,133,206,141,14,212
0033    DATA 141,14,210,141,0,212,141,8
0034    DATA 210,133,204,169,3,141,15,210
0035    DATA 169,148,141,26,208,198,208
0036    DATA 240,10,162,5,202,208,253,157
0037    DATA 30,208,240,23,165,207,133,208
0038    DATA 164,206,185,0,6,9,16,141,1
0039    DATA 210,200,196,203,144,2,160,0
0040    DATA 132,206,198,204,208,215,198
0041    DATA 205,208,211,169,255,141,14
0042    DATA 212,141,14,210,96
0043
0044    DIM QQQ$(1),QQQ(17)
0045    RESTORE 0029
0046    FOR QQL=ADR(QQQ$)+1 TO ADR(QQQ$)+97
0047    READ QQB
0048    POKE QQL,QQB
0049    NEXT QQL
0050    RETURN
0051

7. Custom Waveform Revisited

Last time out some sweeping generalizations were made in this space in the interest of shortening the discussion on waveforms to a digestible length. Let's go back and qualify those remarks.


Here's the truth: Custom waveforms can be used simultaneously with other routines. What's more, they can even coexist with Basic routines. Surprised? Let's review the case against using simultaneous custom waveforms.


(1) "Basic is too slow to change the volume control bits at a usable frequency." Right. Very little can be done at the speed Basic runs at.


(2) "The only way Basic can run simultaneously with a machine language routine is through the use of interrupts." As far as we know, that's also correct.


(3) "Vertical blank interrupts operate at too low a frequency (at intervals of one-sixtieth of a second). The highest frequency obtainable through VBIs would be a 30 Hz square wave." Also true.


Given this, it would be easy to conclude that there was no way to produce a usable custom waveform that would continue to play during the execution of a Basic program. Where this line of thinking goes astray, though, is in the assumption that the only interrupt we have available to us is the vertical blank. It's an understandable mistake: VB1 routines have become so popular that programmers now hang half their codes off of them, and once we discover a useful approach to a problem we tend to think of all similar problems in the same way. Nevertheless, there are lots of other interrupts available—and one type in particular that is tremendously more flexible than a VBI.


Pokey's Interrupt Timers. The very same hardware used to generate standard Atari sound effects has an interesting trick: When Pokey timers 1, 2, and 4 count down to zero, they generate an interrupt request, causing the 6502 to jump through global RAM vectors VTIMR1, VTIMR2, or VTIMR4. The addresses of these vectors are $210, $212, and $214 respectively. Usually, these vectors just point to a PLA, RTI sequence, and so nothing happens as a result of the interrupt.


When one of these vectors is changed to point to an interrupt routine, though, it provides us with a nifty frequency controllable interrupt for time-critical routines„ The old formulas for determining frequency still apply, so all we need to do is determine how often we want the routine to execute, calculate the corresponding AUDF value, and poke it into the frequency registers for that particular channel. We won't even have to worry about reloading the timer, Pokey automatically reloads it with the last value that was poked into AUDE.


Sometimes the capability that was built into this machine just leaves you breathless.


Generating simultaneous custom waveforms becomes terribly easy when viewed in this light. We need three routines: one to set up the interrupt (start the sound), one to clear the interrupt (make it shut up), and the interrupt itself, which will simply pick succeeding values out of a waveshape table and poke them into AUDC. Once the setup routine has been executed, the frequency of the note is selected by Basic and poked into AUDF in much the same way we've been selecting frequencies all along. Basic can pick a note, go away, and do something interesting (the demo draws some pretty boxes), and the sound won't stop until we execute the shut-up routine.


The limitations? Well, there are three. The higher the frequency, the more noticeable the distortion caused by DMA, et cetera. Higher still, the interrupts occur so fast that the machine can't pay attention to anything else, and it locks the program up at AUDF value 2. (You can recover by hitting system reset, and your program will still be intact.)


The third limitation is that the higher your frequency the less machine time will be available to the rest of the program. For instance, using the screen editor while the interrupt is ripping along at AUDF value 3 is weird: The OS becomes so slow you can see each individual line scroll up the screen. Obviously, this effect would be quite unacceptable in a program.


The Demo. The Basic demo installs the three routines just described and then uses them to play a squashed motif from Beethoven's "Moonlight Sonata" while drawing boxes on the screen. The tempo variations are strictly a result of the varied lengths of time required to draw the different size boxes—it is certainly possible to achieve a steady tempo by altering the display routine to one of a more predictable length.


The waveform played is a ramp wave, which produces a nasal-like tone quite different from that of the standard square wave and yet pleasing enough to be used for musical effect. The frequencies used are quite low, to get away from the problems described earlier, but higher frequencies than these are usable.


Both of the listings should be self-explanatory, but two items deserve comment. Note that the Basic routine is careful to poke a frequency value into AUDF before calling the setup routine. This is to make sure AUDF contains a value low enough to prevent lockup of the program.


Also note that the waveform table (in the assembler listing) is eight steps long. Shortening or lengthening this table will raise or lower the frequencies obtained by AUDF. Shortening the table to four steps, for instance, would produce sounds an octave higher than those created by the current version. This is one way to get higher pitches without making the rest of the program sluggish—at the expense of waveform resolution. If you do change the length of the table, remember to change the portion of the code that does the wraparound of the waveform pointer WIN DX.


Get into this program, use the routines in your own work, and here's hoping you get as big a kick out of using them as your correspondent did writing them. This technique, and the possibilities it opens up, is one of those serendipitous discoveries about the Atari that can leave you feeling excited for days.


Basic Interrupt Sound Demo


0001 REM Interrupt Sound Demo
0002 REM (with apologies to Ludwig)
0003 REM Routine addresses and freq register equate
0004 SET=1536:CLEAR=1558:AUDF=53760
0005 REM Poke in machine language
0006 FOR L=1536 TO 1600
0007    READ BYTE
0008    POKE L,BYTE
0009 NEXT L
0010 REM Data bug check
011 READ BYTE
0012 IF BYTE <>-1 THEN ?"Data error.":STOP
0013 REM Set up graphics
0014 GRAPHICS 23
0015 REM Initialize freq first!
0016 POKE AUDF,127
0017 REM Start interrupt
0018 Q=USR(SET)
0019 REM Our first beat
0020 FOR D=1 TO 100:NEXT D
0021 REM Play melody
0022 BYTE=95
0023 POKE AUDF,BYTE
0024 C=C+1:IF C>3 THEN C=0
0025 X=X+7:IF X >80 THEN X=X-80
0026 Y=Y+1:IF Y>48 THEN Y=0
0027 COLOR C
0028 PLOT X,Y:DRAWTO 159—X,Y
0029 DRAWTO 159—X,95—Y:DRAWTO X,95-Y
0030 DRAWTO X,Y
0031 READ BYTE
0032 IF BYTE<> —1 THEN 0023
0033 REM Turn of interrupt
0034 Q=USR(CLEAR)
0035 REM Wait for key press
0036 IF PEEK(764)=255 THEN 0036
0037 END
0038 REM Machine language routines
0039 DATA 120,169,35,141,16,2,169,6
0040 DATA 141,17,2,165,16,9,1,133,16
0041 DATA 141,14,210,208,10,120,165
0042 DATA 16,41,254,133,16,141,14
0043 DATA 210,88,104,96,138,72,174
0044 DATA 57,6,189,58,6,141,1,210
0045 DATA 232,138,41,7,141,57,6,104
0046 DATA 170,104,64,16,18,20,24,31
0047 DATA 16,16,16,-1
0046 REM
0049 REM Melody data
0050 DATA 80,127,95,80,120,95
0051 DATA 80,120,95,80,120,90,71,120
0052 DATA 90,71,127,101,71,127,95
0053 DATA 80,127,95,85,143,101,85
0054 DATA 161,127,95,161,127,161
0055 DATA 192,192,192,-1	


Assembly Language Interrupt and Routine


TITLE 'Interrupt Snd Demo'
;OS equates
IRQEN = $D20E ;IRQ enable
POKMSK = $10 ;shadow for IRQEN
AUDF = $D200 ;freq register
AUDC = $D201 ;volume register
VTIMR1 = $210 ;timer vector
;
ORG $600
;Set and Clear are to turn
;the sound routine on and off
SET    SEI
LDA #LOW TIMRI ;point to
STA VTIMR1 ;sound
LDA #HIGH TIMR1 ;routine
STA VTIMR1+1
LDA POKMSK ;enable it
ORA #1
STA POKMSK
STA IRQEN
BNE SET2
CLEAR SEI
LDA POKMSK ;kill rout
AND #$FE
STA POKMSK
STA IRQEN
SET2    CLI
PLA ;remove args
RTS ;back to Basic
;
;here's the interrupt sound routine
TIMRI    TXA ;save X
PHA
LDX WINDX ;get wave X
LDA WAVE,X ;get audc value
STA AUDC ;& place it
INX ;next step
TXA ;do wraparound
AND #7
STA WINDX
PLA ;restore X
TAX
PLA
RTI
;
WINDX    DS 1    ;wave index
;waveform table
WAVE    DB $10,$12,$14,$18
DB    $1F,$10,$10,$0
END	

8. Pulse Width Modulation

As we continue to search for greater variety in the Atari's sonic repertoire, we will find ourselves dealing with more and more complex waveforms. The Atari's normal mode of operation provides us with probably the simplest waveform, a square wave. In the last few columns we have played with designing custom waveforms such as sine and ramp waves. Now we're going to push one step farther into the jungles of wacka-wacka: dynamic modulation of waveshapes.


The most easily modified waveform characteristic is probably the pulse width. Take, for example, a simple pulse waveform. The pattern might look like this:


10000000


where a 1 means that the speaker is pushed out at some distance and a 0 means that the speaker is at rest. This kind of shape produces a very thin, nasal-like tone, because the speaker is mostly at rest and is only occasionally being popped out. To stretch out the pulse width, we would just lengthen the time the speaker was pushed out:


11110000


This shape sounds fuller, because the speaker is spending equal amounts of time in each position. When talking about the pulse width, we commonly refer to the percentage of the relationship between in and out. Hence, the waveform pattern just described is called a 50 percent duty-cycle pulse. Fifty percent is the widest pulse we can obtain, because a 75 percent duty cycle (11111100) sounds just the same as a 25 per cent duty cycle (11000000).


If this doesn't make any sense, bear in mind that the important factor in sound is not the amplitude itself but the change in amplitude. A speaker held out at the maximum position makes no more noise than a speaker held at rest—it's the movement of the speaker that creates noise, not its position. Now go back and look at the 75 percent and 25 percent waves and notice that if you invert each of the positions (turn 1s into 0s and vice versa) you get the same pattern. Our ears will not be able to detect any difference.


"Ah," you say, "but doesn't widening the pulse change the pitch?" Not so! In every case shown so far, the pattern takes eight steps to repeat: The frequency will thus stay the same.


Since changing the pulse width affects the subjective quality of the tone, we can now predict what would happen if, while playing the waveform, we simultaneously stretched its shape. If the waveform started out as a narrow pulse (like the first example) and ended up as a square wave (the second), we would hear the tone swing from a thin, reedy sound to a full-bodied tone. This is an interesting and useful effect vaguely reminiscent of the Tesla coil sound from the early Frankenstein movies. (Remember the funny little device that made a high-voltage electric arc more up and down?) What's more, because we’re not doing anything funky with the pitch, pulse width modulation is musically useful, too.


And, in case you haven't guessed, that's what we’re going to do.


The Assembler Listing. The machine code in listing 1 sets up the same frequency-controllable interrupt we discussed last time. The only thing different is some additional initialization and, of course, what happens inside the interrupt routine. The safest thing to do here was to have the setup procedure poke 255 into the audio frequency register so that when the interrupt was enabled the machine wouldn't lock up due to too high a frequency.


This is safe, but it means that you have no choice over the pitch that comes on when the set routine is first called. To correct this, a crude volume control at location 203 (decimal) was added, and this location is initialized to 0 by the setup procedure. Poking a 15 into this register will cause the waveform to be played at its loudest volume. Note that poking a 0 into this register stops the tone but does not stop the interrupt, and is not a replacement for the clear interrupt procedure!


The sound routine uses bit manipulation to obtain the pulse width modulation effect. An explanation of the assembler code follows, but the listing should also be studied. If you are a beginning assembler programmer, pay close attention to the way the carry register is used.


STEP keeps track of what portion of the waveform the routine is currently playing and contains just a single bit perpetually moving to the right. This pointer is ANDed with the waveshape variable WIDTH to derive the value to be sent to the speaker. To get the ramp wave, we just replace the arbitrary "on" amplitude discussed earlier with one derived from the routine's wavestep position (STEP again). This means that the first time through the routine the on amplitude will be 8, then 4, and so on. This is all we need to play the waveform; the rest takes care of the modulation.


Next we update a counter to control the rate of modulation. When the counter goes to 0, the first thing we do is check to see if the modulation rate has reached its maximum value, $80; if it hasn't, the rate is slowed down. This portion was added to make the modulation more interesting than a steady sweep speed. When a 1 is poked into MRATE, the pulse width changes at a furious rate, eventually slowing down to a slow, steady sweep. The Basic demo uses this to provide a certain amount of expression. Note, too, that if you poke MRATE with a number greater than $80, the sweep speed is simply set to that rate. The Basic demo uses this in line 55 to start the music off with a very slow shift.


The only thing remaining to do is to change the pulse width. This is done by examining the third bit from the left of the byte WIDTH and inverting its state, then rotating all the bits to the left so that the new bit comes in from the side. This gives us a marching parade of bits: First three 1s go by. then three 0s. then three 1s again. The maximum width of three bits was chosen to ensure that there would always be a change of state somewhere in the waveform, thus preventing gaps of silence in the tone.


Finally, if you're going to play music with pulse width modulation, you need to know which values produce an equally tempered scale. Listing 2 will allow you to print out a chart of these values. The formula for determining the frequency of the interrupt is the same as that discussed previously, but note that the calling rate of the sound routine and the pitch produced by that routine are two different things. In this case, the routine has to be called six times before the waveform is complete (three 0s and then three 1s), and so the output frequency is accordingly divided by six.


Listing 3 gives a fairly obvious use of the routines. The only unusual feature in it is the use of negative pitch specifications: Whenever a negative pitch is read from the table, this is interpreted as a signal to tweak the modulation rate, which in turn causes a burst of tonal activity. The frequency values are also used to determine the color and position of the lines drawn on-screen. Once again, the Basic demo is intended as a springboard only, to encourage you to use the real meat in the assembler listing and to experiment with it.


Next time around, were going to change the pace a bit with a look at a very nice alternative to coding in Basic or assembler: the language C.


TITLE 'PWM 1.0'
;
;Pulse Width Modulation Demo
;*Assembler Listing*
;
;OS equates
IRQEN = $D20E ;IRQ enable
POKMSK = $10 ;shadow for IRQEN
AUDF = $D200 ;frequency register
AUDC = $0201 ;volume register
VTIMR1 = $210 ;timer vector
RANDOM = $D20A ;random # register
;
;Zero page variables
VOLUME = $CB ;master volume
MRATE = VOLUME + 1 ;modulation rate
MODC = MRATE + 1 ;modulation count
WIDTH = MODC + 1 ;pulse width
STEP = WIDTH + 1 ;wave step count
ORG $600
;
;Refer to the July '83 Atari Sound
;column for a description of the method
;used to obtain an interrupt-driven
;sound routine.
;
;Enter here to start sound
SET SEI
LDX #$FF ;ensure no lockup
STX AUDF ;upon start
INX
STX VOLUME ;zero volume
INX
STX STEP ;initialize step
STX WIDTH ;and pulse width
LDA #LOW PWM
STA VTIMR1
LDA #HIGH PWM
STA VTIMR1 + 1
LDA POKMSK
ORA #1
STA POKMSK
STA IRQEN
BNE SET2
;Enter here to kill sound
CLEAR    SEI
LDA POKMSK
AND #$FE
STA POKMSK
STA IRQEN
SET2 CLI
PLA
RTS
;
;Here's the new pulse width modulation
;sound routine:
;
PWM LDA STEP ;get position count
LSR A ;update it
BCC NWRAP ;if bit falls off,
LDA #8 ;replenish it
WRAP STA STEP ;save updated pot
AND WIDTH ;examine waveform
AND VOLUME ;clip bits for vol
ORA #$10 ;set forced output
STA AUDC ;poke speaker
DEC MODC ;check if time
BNE NOMOD ;to modulate
LDA MRATE ;recharge count
STA MODC ;with mod rate
BMI NACCL ;if > $7F, skip
INC MRATE ;slow down mod rate
NACCL CLC ;change pulse width
LDA WIDTH ;examine d2
AND #4 ;… if set.
BNE CCLR ;then carry clear
SEC ;else set carry
CCLR ROL WIDTH ;rotate carry in
NOMOD PLA ;ta-da
RTI
;
END	

Listing 1.


0005 REM This program will print
0010 REM all the equally tempered
0015 REM notes available from the
0020 REM PWM assembler demo
0025 DIM N$(25)
0030 ? "Turn on printer, press RETURN"
0035 INPUT N$
0040 LPRINT "Note","Value", "Frequency"
0045 LPRINT
0050 REM Note placement of spaces
0055 N$ = "C C#D D#E F F#G G#A A#B"
0060 FOUT = 8.1759375:SKEY = 1
0065 CLOCK = 63921
0070 AUDF = INT((CLOCK/(FOUT*12))- 0.5)
0075 IF AUDF > 255 THEN 0090
0080 IF AUDF < 23 THEN 0110
0085 LPRINT N$(SKEY,SKEY + 1),AUDF,INT(FOUT*100)/100
0090 SKEY = SKEY + 2
0095 IF SKEY = 25 THEN SKEY = 1
0100 FOUT = FOUT * 1.059463094
0105 GOTO 0070
0110 END	

Listing 2.


0005 REM ********************
0010 REM *     PWM Demo     *
0015 REM *                  *
0020 REM ********************
0025 REM
0030 GOSUB 0235
0035 REM Start up interrupt
0040 Q = USR(ION)
0045 RESTORE
0050 A = 243:COL = 15
0055 POKE PWM,255
0060 POKE AUDF,ABS(A):POKE VOL,15
0065 IF A< 0 THEN POKE PWM,20:COL=15
0070 SETCOLOR 2,COL,O;IF COL>0 THEN COL = COL - 3
0075 X=ABS(A)/1.5:Y=Y+8-COL/2
0080 IF Y > 191 THEN Y = 0
0085 PLOT 0,0:DRAWTO X,Y:DRAWTO 160,96:DRAWTO 319 - X,Y:DRAWTO 319,0
0090 PLOT 0,191:DRAWTO X,191 Y:DRAWTO 160,96:DRAWTO 319 - X,191 - Y:DRAWTO 319,191
0095 READ A
0100 IF A <> 0 THEN 0060
0105 REM Note table
0110 DATA 204,172, - 144,102,85,72
0115 DATA 60, - 45,42,- 35,37,45,47
0120 DATA -33,35,42,45,26,31, - 37,45
0125 DATA - 53,31,37,45,57,57, - 42,67
0130 DATA 57,67, 85,136, - 114,136
0135 DATA 172,172,172,172,172,172
0140 DATA 172172,0
0145 REM Turn off interrupt
0150 Q=USR(IOFF)
0155 GOTO 0155
0160 REM Machine code follows
0165 DATA 120,162,255,142,0,210,232
0170 DATA 134,203,232,134,207,134
0175 DATA 206,169,48,141,16,2,169,6
0180 DATA 141,17,2,165,16,9,1,133
0185 DATA 16,141,14,210,208,10,120
0190 DATA 165,16,41,254,133,16,141
0195 DATA 14,210,88,104,96,165,207
0200 DATA 74,144,2,169,8,133,207,37
0205 DATA 206,37,203,9,16,141,1,210
0210 DATA 198,205,208,18,165,204,133
0215 DATA 205,48,2,230,204,24,165
0220 DATA 206,41,4,208,1,56,38,206
0225 DATA 104,64
0230 DATA -1
0235 REM Poke in machine code
0240 GRAPHICS 24:COLOR 1
0245 SETCOLOR 1,0,12
0250 RESTORE 0160
0255 FOR L=1536 TO 1625
0260 READ A
0265 POKE L,A
0270 S=S + A
0275 SETCOLOR 2,0,14 - INT(S/736)
0280 NEXT L
0285 READ A
0290 IF A <> -1 OR S <> 10309 THEN ? "Error in machine code": STOP
0295 REM Machine code entry points
0300 ION = 1536;IOFF = 1571
0305 REM Special register equates
0310 VOL = 203:PWM = 204:AUDF = 53760
0315 RETURN

Listing 3.



9. Untitled

As this column has progressed, we have looked at some simple sound techniques in Basic and a few advanced waveform tricks in assembly language. Along the way, we have encountered the eternal tradeoff between ease of programming in Basic and machine speed in assembler. This month were going to wrap up our discussion with a look at a medium-level language perfectly suited to working with sound: C.


If you have a copy of C, this column should give you a good starting point for playing with pink-noise music and clear up at least one surprise you may encounter in C sound. If you don't have a copy of C. this article will demonstrate some of the power of the language and maybe encourage you to get a copy. The sample program was developed with the Deep Blue C Compiler by John H. Palevich, sold by the Atari Program Exchange (catalog number APX-20166).


1/f Noise and Richard Voss. The application we’re going to look at involves a different approach to random music, called 1/f noise. This algorithm is an attempt to solve one of the stickiest problems inherent in random composition: lack of order. The very nature of a random number generator is chaotic; most music is not. If we write a program to randomly select a bunch of candidate notes and play them, the result is very hard to listen to. The probability of any one note being picked is equal—this is the musical equivalent of white noise.


Mathematician Richard Voss came up with a very popular algorithm to "filter" random numbers to give them a more natural distribution rate. This simple yet elegant algorithm produces a flow of values that mirror natural processes (wind and rain, snowflakes, et cetera) in their tendency to combine small changes with occasional large jumps in value. This is useful in many more situations than music: Say you're writing a video game that has a spider that moves more or less randomly. Substituting the Voss algorithm for a conventional random number generator produces a remarkable difference in the quality of the spider's movement; he seems a little more intelligent.


To picture how these "voss numbers" work, image a row of four dice, all of them with the 1 face up. We read them in decimal order to produce a total of 1,111. Now we roll the die in the "ones" position. This will give us six possible values, ranging from 1,111 to 1,116. But if we roll the die in the "tens" position, we get six possible values ranging from 1,111 to 1,161. Instead of a possible value change of five, we get a range of fifty.


One more time: Let's roll both the dice in the "tens" and "ones" position. Now we can get thirty-six possible values, ranging from 1,111 to 1,166—a possible change of fifty-five. Finally, when we work our way up to rolling all four dice, we get 1,296 possible values (6 to the fourth power) with a value range of 5,555.


Now to show the pattern of die rolls more explicitly. In this chart, a "1" means to roll the die, a "0" to let it stand.


0001 0010 0011 0100 0101 0110 1111 1000


which is, of course, the familiar binary counting pattern!


To use this system of dice rolling in a program, then, we need two variables for each parameter. We need one variable to keep track of the current "state" of all the dice: In this variable each bit will represent a single two-sided die (okay. maybe it's a coin). Then we need a second variable that we'll call our "filter." This variable will tell us which dice we get to "roll"—that is, which of the bits in the dice variable we get to change. To roll a particular die, we will use the random number generator to decide if that bit is a 1 or a 0. Finally, every time we get a new number we have to increment the filter variable to update our dice-rolling pattern.


Sound complicated? If you don't have bit manipulations in your Ianguage, it is. But in C. it becomes terribly easy. Watch:


We'll store the dice and filter variables in an array—remember that we want to keep track of the dice and filters for each parameter that will be using the function (volume. tempo, pitch, et cetera). Each of these variables will be one byte in length. In C. that's called a character, and we have to declare that as the data type.


char d[12]; char f[12];


This gives us thirteen parameters to play with (C uses the 0 element). Note that arrays are formed with brackets, not parentheses.


Now let's write the function.


+ + f[p]


This phrase automatically increments the filter element that "p" is pointing to. In Basic, we would say "F(P) = F(P) + 1". The phrase:


peek(RANDOM)& + + f[p]


simultaneously increments f[p] and then does a logical AND (&) of the result with the random number generator. (This assumes that RANDOM was previously defined as hex D2OA, which is the memory location of the random shift register.) The result of this operation is that wherever a bit was 0 in the filter, we'll get a 0 in the output, but wherever a bit was 1 in the filter, we'll get a randomly determined 1 or 0 in the output: Some bits in the random number are forced to zero. This gives us our random die roll.


Now we want to graft the new dice state on with the old dice state. In C, the character "^" does an exclusive OR (EOR in assembler). If our new die bit is a 0, then, the old die bit is left untouched. If the new die bit is a 1, the old die bit is inverted: a 1 becomes a 0 and vice versa. This has the effect of only "rolling" the dice that correspond to 1s in the filter variable.


d[p] = d[p] ^ (peek(RANDOM)& + + f[p]);


But we have another shortcut in C. Programmers are always saying things like "A = A + 10" or "ZOT = ZOT*25". By placing the operator before the equals sign, we can omit repeating the variable name: thus we get "a + = 10" and "zot* = 25". If this seems like a frivolous touch, try writing the Basic equivalent to "r[3.14*g[x + = 10/y]]* = 22;".


d[p] ^ = (peek(RANDOM)& + + f[p]); does the same thing as the previous die-rolling phrase; it's just abbreviated. This does everything we asked our "voss" function to do. For the program, we'll add one more tidbit: a divisor (r) to chop down the end result. If r is 2, the highest possible value will be 127 instead of 255. Finally, we'll bundle the whole expression up inside the "return" statement, which ends the function and returns the value of the expression. The final form of our function can be seen at the bottom of the demo program listing.


It's expressions like this where C really shines. Try to duplicate this one-line function in Basic; you'll be amazed at the expansion that takes place.


The conciseness of the language is enhanced in other ways. For instance, every expression has a value: the value that was assigned to the left-hand side of the expression. This makes statements like "a = b = c = d = 0" legal, for the expression "d - 0" has a value of 0, which forms the implied expression "c = 0", and so on. If you look near the bottom of the function "main", you will see this statement:


plot(x = voss(10,2),y = voss(11,3));


which calls the function "voss" and assigns the returned value to x, calls "voss" again and assigns the value to y, then calls the function "plot", and passes it the new values of x and y. This kind of compression is incredibly powerful, once you get used to it


A Walk through the Demo. The first part of the program is one long comment that describes the linking file necessary to compile the program. In C. remarks are set off with a single "/*", and the remark does not end until closed off with a "*/".


The "#define" statements are simple text-replacement commands. Wherever the string ""Parms'" is encountered, the statement will be processed as if "12" had been typed, Note this is a label definition and has nothing to do with variables: None of your run-time memory is wasted.


Next come the array declarations. These are made above the first function definition so that they will be accessible, or "global", to all following functions. C has a comprehensive set of rules to protect the "scope" and "privacy"' of variables. Nonarray variables, for example, are usually passed by value, so a called function gets its own "private copy" of the variable that it can modify without screwing up the calling function's copy.


The function that is to be run when the program is loaded must be named "main". This is followed by an empty parameter list (the parentheses) to indicate that no parameters are passed to this function. The strings "$(" and "$)" are accommodations to the Atari, which does not have braces. Logical statements are grouped together with braces: When the last brace is closed off, the program ends.


In the initialization, we first correct a minor bug in C, and give POKEY the values that will let us use channel 4 without interference. Failure to do so will leave you with a three-voice Atari. Then we give our dice and filters some random starting values, copy the string of candidate notes into the array "fn" (the note-to frequency specification is given in the form of ATASCII characters), and set up the graphics for a hideously bright blue.


After that, the only thing left is to go into an infinite "while" loop, play the voices, and draw a few lines on the hideously bright blue. C has three types of loops. The familiar for-next has become the "for" loop. The following statements are equivalent:


for(voice = 0;voice < 4; + + voice)


100 FOR VOICE = 0 to 3 STEP 1


C also has a "while" loop that continues as long as the expression is true, and a "do while" loop that does the same but puts the test at the bottom instead of the top. Oh, yes: Unlike Basic. C’s "for" loop makes its test at the top, where it should be. The statement inside this loop. . .


for(x 5;x<0; + + x)


will never be executed.


Happy Trails. And so we ride out of the range of the Circle C and wrap up our overview of the sound programming possibilities of the Atari computer. Hopefully, using techniques presented here over the last year and a half, you've been able to elicit some intriguing tonal effects from your machine, and gone on to come up with some techniques of your own. Glad we could help.


May all your game programs, present and conjectured, be neither sonically meek nor audibly irritating.


/*Music In C

by Bill Williams

If the name of this file is CMUS.C,

your linking file (CMUS.LNK)

should look like this:

cmus

aio

graphics

dbc.obj

*/
#define PARMS 12
#define RAND 0xD20A
*define TRUE 1
#define SKCTL 0xD20F
#define SSKCTL 0x232
#define AUDCTL 0x0208
char d[PARMS]; /* dice */
char f[PARMS]; /* pinking filters */
char fn[10]; /* freq/note table */
char    vol[4]; /* volume counts */
char note[4]; /* voice’s note */
main()
$(
int    i,voice,tempo,x,y;
/*Initialize Pokey for sound */
poke(SSKCTL,3);
poke(SKCTL,3);
poke(AUDCTL,0);
/* Initialize pink numbers */
for(i = 0; i <= PARMS; + + i)$(
d[i] = peek(RAND);
f[i] = peek(RAND);
$)
/* Set up note table.
Current candidate notes
make up a two-octave pentatonic
scale.
*/
strcpy(fn,"!%*29DLUfr");
for(voice = 0;voice < 4;+ + voice)
  note[voice] = fn[rnd(10)];
/* Set up graphics */
graphics(24);
color(1);
setcolor(1,8,0);
setcolor(2,8,12):
/* Play Music */
while(TRUE)$(
for(voice = 0;voice < 4; + + voice)
play(voice);
for(tempo = voss(9,6);tempo > 0;- - tempo);
if(!rnd(8))$(
plot(x = voss(10,2),y = voss(11,3));
drawto(191,peek(RAND)&0xC0);
drawto(319—(x/2),y):
$)
$)
$)
play(v)
char    v; /* voice index */
$(
/* if volume is not 0, play voice
and decrement volume. Otherwise,
pick a new starting volume and a new note.
*/
if(vol[v])
  sound(v,note[v], 10,vol[v]--);
else $(
vol[v] = 3 + voss(v,31);
note[v] = fn[voss(v + 4.28)];
$)
$)
/* Voss' 1/f noise algorithm */
voss(p,r)
char p; /* parameter number */
char r; /* range (divisor) */
$(
return((d[p]^ = (peek(RAND)& + + f[p]))/r);
$)