Classic Computer Magazine Archive COMPUTE! ISSUE 23 / APRIL 1982 / PAGE 162

INSIGHT: Atari

Bill Wilkinson
Optimized Systems Software
Cupertino. CA

This month, I present a session on how to steal a system. Before all you kleptomaniacs rejoice, though, I should explain that I mean to show you how to take control of your Atari's software system when a user pushes the SYSTEM RESET button. This will, I hope, be useful to BASIC and assembly language programmers alike.

There will be more on the inner workings vs. outer appearances of Atari BASIC; and, as space permits, I will have my usual assortment of cute tricks and Did You Knows.

In a departure from the norm, I will review a product here. Since my company, Optimized Systems Software, both solicits and sells software for Atari (and Apple) computers, I do not think it would be fair for me to do software reviews. But, unless you and/or my dear editor object, I may, from time to time, discuss new and wondrous happenings in the world of Atari.

A Short Review

It generally strikes me as unfair for a magazine to carry a review of something it sells; but every other magazine does it, so I prevailed on COMPUTE! to let me review COMPUTE!'s First Book of Atari. I am doing so on the condition that the review must be run as I submit it. (Okay, okay, Richard…you can fix my punctuation.) I have to do a good review or they won't let me do it again (just kidding… think).

First, let me say that I did not start reading COMPUTE! regularly until about December, 1980, so most of the material in the book was new to me. Boy was it new to me! Quite frankly, I had lulled myself into thinking that, until I started writing my column, the poor Atari user had no insight (an insight gag) into the workings of Atari BASIC, DOS, etc. Not so!! There was a lot of good stuff published in COMPUTE! during 1980.

I don't want to make this review sound like a whitewash, so let's get the bad stuff over with first. The first warning that needs to be given is that, in general, this is not a book for software hackers: there is little of interest to the assembly language programmer (but see below for some notable exceptions), and the person who has read and understood (II) the technical manuals and, perhaps, De Re Atari won't find much he or she didn't know. However, for most people there is much useful material here. There are some bloopers in the material presented, things which probably wouldn't get past the current, more Atari-sophisticated, editorial staff. There's a little duplication of material. And there's a lot of stuff that has been updated by better articles which have appeared (usually in COMPUTE!) in 1981 and 1982. Actually, my biggest complaint is in a reprinted article titled "The OUCH in Atari BASIC": the article states, and the editorial lead-in agrees (and the lead-in was written recently – Oh, for shame!), that keywords can't be used in variable names. Yet, the very next article in the book says that all keywords can be used as variable names! (Still not quite true – "NOT" is poison as the first three characters of a name, and a few keywords, such as "TO" and "STEP" can't be used as-is. Oh well, this was 1979 and 1980. And, come to think of it, even Atari's BASIC Reference Manual still says not to use keywords as names. Of course, it also says that substrings are limited to 99 characters, so maybe it's not a good reference point.)

OK. So much for the bad stuff. "Not possible!." you scream? Sorry, but it's true. I really don't have any dirt to sling. Oh, some of the little example programs might now be found in the Atari manuals, etc., but they aren't bad, just not of as much value as the rest of the book. And I wish I had the time and space to correct every little goof I found. (But I gotta tell you one: the order and size of variables and their names has no impact at all on the speed of an Atari BASIC program. Honest.) With those caveats in mind, we examine the value of the book.

And the book is of value. If you had to choose between losing your left pinkie (not quite up to the left thumb, anymore), the First Book, and De Re Atari, you should really think about how useful a little pinkie is. If you must choose between De Re and First Book, let your experience level be your guide: if you almost understand the Atari technical manuals, you are probably ready for De Re. If you are just learning to program, stick with First Book. If you're in the middle, better let the little pinkie go.

My own favorite pair of articles from the book are "Inside Atari BASIC" and "Atari Tape Data Files," both by Larry Isaacs. I am just now getting to the point where I am discussing things in "Insight: Atari" that Larry explored over a year ago (there will be overlap, hopefully to your benefit). Other articles worth mentioning include the following (an asterisk indicates something of interest to assembly language buffs):

"Printing to the Screen from Machine Language"*
(not because of what the presented program does as much as for some of the techniques it introduces)

"The Fluid Brush"
(Ditto. And its ideas have been much copied.)

"Player/Missile Graphics" *
(by Chris Crawford. What more need I say?)

"Adding a Voice Track to Atari Programs"*
(This one was even swiped! There are more sophisticated methods shown in De Re, but this is adequate for many purposes.)

"Atari Memory Locations" *
(Just a table. You need to read the Technical Manual and/or De Re first, but this will serve as a handy reminder.)

"Input/Output on the Atari"
(I hesitated on this one: you should ignore what it says about XIO! It's misleading. Read my Atari I/O series.)

You'll note that most of that stuff is kind of heavyweight. Well, that's what appeals to me and, I think, to a large portion of COMPUTERS Atari readership. However, there are several little goodies, usable by virtually anyone, which deserve honorable mention. No commentary on these: their names tell it all and you just have to try them to appreciate them:

"Reading the Atari Keyboard on the Fly"
"Al Baker's Programming Hints"
"Atari Sounds Tutorial"
"Using the Atari Console Switches"
"Atari Meets the Real World"

You may have your own favorites, but my criterion for a good article (or good magazine-published program) is that it teaches you something. Thus I rate tvpe-it-in-and-run-it games relatively low. (There are remarkably few of them that appear in the book.)

In final summary, I have to say that, for $ 12.95, you are unlikely to find this much (184 pages, including –can you believe it –a usable index) useful Atari material presented again (well…until the Second Book ?). Real software hackers will find some of the material too elementary, but they are probably the only ones that will be disappointed.

Stealing A System

During my series on Atari I/O (COMPUTE! November, 1981, through March, 1982, issues 18 through 21), I mentioned (more than once) the "proper" way to add device drivers to OS. I summarize it here again:

  1. Inspect the system MEMLO pointer (at $2E7).
  2. Load or relocate your routine at/to the current value of MEMLO.
  3. Add the size of your routine to MEMLO.
  4. Store the resultant value back in MEMLO.
  5. If your routine is a device driver, connect it to OS by adding its name and address to HATABS.
  6. Fool OS so that steps three through five will be re-executed if SYSTEM RESET is hit.

In COMPUTE! (January, 1982, #20) we added the driver for the "M:" device by following steps one through five as above. We discussed step six briefly, but did not show how to implement it. This month, we will show how to fool OS. And, rather than repeating the lesson about adding device drivers, we will take this opportunity to show how to give Atari BASIC some measure of control over what happens on RESET.

In particular, we "steal" the system in a way that the user who hits RESET will cause a TRAPpable error in the running BASIC program. In other words, if you write your BASIC program in a way that TRAP (to a line number) is always active, you will be able to detect when your user hits the RESET key, but your program will not stop running, will not lose its variable values, and will be impacted in the minimum possible way.

Some cautions are in order (it seems like I always have to say that): before vectoring through RAM (and thus allowing our little trick) Atari's OS ROMs perform several actions when SYSTEM RESET is hit. If you need to know exactly what happens, try to get hold of the CIO listings (they are moderately readable); generally, the following lists all that matters except to those who would make their own cartridges:

  1. The system resets any memory size pointers (MEMLO, MEMTOP, etc.).
  2. Most hardware registers are reset to zero ($D00()-$D0FF, $D200-$D4FF).
  3. OS clears its own RAM ($200-3FF, $10-$7F). Note that this zaps all IOCB's for all files.
  4. All the ROM-based device drivers are initialized (via their own initialize routines).
  5. CIO's initialization is called, which effectively marks all files as properly closed.
  6. Screen margins, etc., are reset and the E: device is opened on file channel #0 (which is equivalent to GRAPHICS 0 from BASIC).
  7. The file manager's initialization routine is called via an indirect call through location DOSINH ($0C).
  8. If there are no cartridges installed, then DOS is invoked by an indirect jump through location DOSVEC ($0A). If a cartridge is installed and wants control, though, OS goes to the cartridge instead of DOSVEC.

(NOTE: OS/A+ uses a variation on 7., above, so don't bang your head against the wall trying what is written here with OS/A +. I will be glad to tell you of the differences if the manual is not clear enough.)

Program 1 takes into account not only all of the above, but also the requirements of Atari BASIC related to executing a pseudo-warmstart. I will not try to explain why the various JSRs and tests shown are needed; just take my word for it that they are indeed necessary (I found out the hard way). Actually, the part pertaining to stealing the DOSINIT vector is straightforward, as you may note, and changing MEMLO is trivial.

Once again, for space and time reasons, I have cheated with this program: I have assumed that my routine can load and execute at $IF00 and can move MEMLO to $2000. Those of you who want to do the whole thing right can follow the techniques I showed in COMPUTE! February, 1982, #21, for generating relocatable programs. Also, please note that the listing, as is, is designed to produce an AUTORUN.SYS file. You may need to do a little surgery to use it in other ways (e.g., remove the load-and-go vector at the end, JMP directly to the start of the BASIC cartridge, etc. – experiment).

The most important thing to note about this routine's implementation is how the address found in DOSINIT is moved into the JSR instruction (at the label RESET). Obviously, you could go look at the contents of DOSINIT and code the JSR directly, omitting the move of the address. And this will work as long as you use the same version of OS and DOS. But … all too many Atari software developers fell into the trap of thinking that OS and DOS were immutable, only to have Atari announce DOS 2S and OS version B.

To Atari's credit, they have carefully documented which locations, vectors, etc., are guaranteed to remain unchanged. It you write your code properly, you should never have to change it. Incidentally, another example of this same concept appealed in my last article: the vector from VVBLKD was preserved, rather than simply JuMPing to the routines in ROM.

Enough preaching: investigate the listing. But I leave you with one last freehee. If you change three lines of code in the listing (lines 1950 to 1970) to the following two lines, you will cause BASIC to re RUN the program currently in memory, rather than causing an error TRAP.

1950 JSR $B755
1960 JMP $A962

The following short BASIC program illustrates the use of our stolen pointer:

10 TRAP 100
20 PRINT "LOOPING AT LINE 20"
30 GOTO 20
40 STOP : REM
   can't get here from there … or anywhere
100 IF PEEK(195)<>255 THEN PRINT "HOW? WRONG ERROR CODE!" : STOP
110 PRINT "RESET KEY WAS HIT…I WILL START AGAIN"
120 RUN

Note that you can't get out of this program with the RESET key! Now, if you also trap the BREAK key, the user is truly locked in your program. If you have BASICS A+, of course, you tan trap the BREAK key via SET0. If not, then refer to the listing titled "Idiot-Proofing the Keyhoard" in De Re Atari. (Summary of that listing: since the BREAK key is one of the two IRQ's not vectored through RAM, you must change the system master IRQ vector to point to your own routine. In your routine, you check for and ignore BREAK key IRQ's and pass other IRQ's on unchanged. Not trivial, perhaps, but certainly less complicated than what we have done above.)

Inside Atari BASIC, Part 3: Enhancements

After skipping last month, we return to our discussion of the hows and whys of Atari BASIC. Recall that in COMPUTE! February, 1981, #21, we discussed how BASIC checks your entered line for correct syntax and produces a tokenized result. Let us begin this month with a discussion of how BASIC executes (RUNs) a program.

First, note that if you enter a direct line (one without a line number), BASIC arbitrarily assigns it to line number 32768 and then pretends that it is like any ordinary line. That means that even direct lines must go through the tokenizing and execution process. It also means that BASIC makes little or no distinction between statements (within a program) and commands (given directly); thus you can LIST or RUN or even CONTinue from inside a BASIC program.

Whenever a line is finished executing, BASIC checks to see if the next line exists (it doesn't if a direct line was just executed) or if the next line has a line number greater than 32767 (i.e., if the line executed is the last one prior to the direct line). If either condition prevails, BASIC pretends to itself that it got an "END" statement and, presto, you are back staring at the "READY" prompt.

But let us assume that the direct command given was "RUN." The execution of a RUN statement simply causes all BASIC'S pointers and flags to be reinitialized, including setting BASIC'S "next line" pointer to the beginning of the program. Then RUN returns to what we call "Execution Control" which decides that it needs to start executing the next line…which conveniently is the first line.

So far, so good. But how does BASIC know what to do with the tokens? The answer is that it doesn't, really. Recall that there are two separate kinds of execution (as opposed to variable) tokens: statements and operators. Each of these has a table of two-byte pointers residing in BASIC'S ROM. Execute Control simply picks up the next byte of the program, assumes that it is a statement token (incidentally, in the range of $00-$7F), and uses double its value as an index into the table of statement pointers. It uses the address thus found as an indirect jump and goes to the appropriate statement execution routine.

In a non-syntaxed BASIC (i.e., Microsoft), much of the preceding applies virtually unchanged. But, when the statement execution routine gets control in such a BASIC, it has no idea what the next character or token in the program might be, so it must needs go through a set of checks to determine what is legal and what is not.

In Atari BASIC, though, the statement execution routine knows that the byte(s) that follow constitute legal syntax! So it need not waste time checking for legality. Since the bytes following the statement token may range from the non-existant (as in CONT, which has no following bytes) to the extremely complex (as in PRINT, in all its variations), each statement generally has responsibility for choosing what to do with these bytes.

With the exception of assignment-type operations (LET, READ, INPUT, etc.), file designators (PRINT #), and complex statements (FOR…TO…STEP), what follows the statement byte is generally a series of one or more expressions, separated by commas, equal signs, semicolons, etc. Thus it comes as no surprise that there is a major subroutine in Atari BASIC entitled "Execute Expression," which can evaluate virtually any numerical or string expression.

As a simple example, let us examine the mechanism of POKE. The syntax is properly "POKE <aexp>,<aexp>" (where <aexp> means Arithmetic EXPression). So POKE's statement execution routine simply calls Execute Expression for the first value, saves it away someplace safe, skips the comma (it knows the comma is there…he syntaxer said so!), calls Execute Expression for the second value, and stores the second into the memory location designated by the first. Now, in truth, POKE calls a variation on Execute Expression which is guaranteed to return a l6-bit (or 8-bit, as required) value; but the concept holds for most statements.

It is really beyond the scope of this article to try to explain the intricacies of Execute Expression. It will suffice to point out that it must worry about operator precedence ("*" before " + ", etc.) and parentheses and subscripted variables and functions (SIN, RNI), etc.) and more.

And that's about it. Except to note that when a statement is finished it usually simply returns to Execute Control, which checks for another statement on the same line and/or moves its pointer to the first statement of the next line.

Much of the point of this discussion has been to show why it is hard to fool BASIC into believing that it has a new statement to use. Even with the source, it is no easy task to make sure that the correct syntax for a new statement is entered into the syntax tables (which are actually a miniature language in their own right), the name tables, and the execution tables (to say nothing of writing the code to execute the statement). With Atari BASIC locked in ROM, the task is really impossible since BASIC makes use of no RAM-based pointers or indirect jumps throughout this process.

So how can we add features to Atari BASIC? Several ways:

  1. Try the USR function as suggested last month. This really is the simplest, most straightforward, most guaranteed-to-work.
  2. Make your own special device handler (a la "M:" in COMPUTE!, January, 1981, issue #20). Open a channel to it (OPEN #1,…). PRINT something to it. When your driver gets control, it can actually go in and look at the BASIC tokens and decide what to do from there. Cryptic, but it works.
  3. If you are interested in commands, as opposed to statements, you can intercept BASIC's call to "E:" (for the next input line) and examine the line yourself (presumably as does Monkey Wrench). This implies that you must check syntax, find variables, convert ASCII to floating point, etc., in your routine. Tedious, but obviously feasible and usable.

As you can, no doubt, tell, I am much in favor of method 1. It is by far the easiest to do and requires the least knowledge of BASIC'S internals.

Is there yet another way? A month ago I would have said "no!" But, now, I have discovered a crack in the door. It is complicated, prone to programmer error, fairly inflexible, and of doubtful value for anyone but professional software developers. To explain it would take a couple of more columns, and I'm simply not willing to write that much on a topic of dubious value. If you feel you absolutely must know, write me (care of OSS). If enough people write, I may make up a pamphlet and sell it at an outrageous price. Are you sure you can't live with method I ?

Easy Horizontal Joysticks

If you have an application (a polite way of saying game) that needs a joystick that moves only horizontally (or only vertically, if you are willing to hold your joystick turned 90 degrees from "normal"), then have I got a trick for you! Try this program, with joystick number 0 plugged in:

10 PRINT PTRIG(0)-PTRIG(1),
20 FOR J = 1 TO 50 : NEXT J
30 GOTO 10

Now push the joystick in all directions. Neat? Pushing it left gives you a value of -1 and right gives you + 1. And, of course, you can use A = PTRIG(2)-PTRIG(3) to read joystick number 1, etc.

Why does it work? Because the paddle triggers happen to use the same pins on the connector that the horizontal switches in the joystick use. I discovered that by reading the technical manual; so, you see, there is probably still buried gold in those books.

Unfortunately, no such happy coincidence exists for reading the vertical joystick switches. Incidentally, use of this trick does not affect STRIG in any way.

Dissonances

The algorithm Atari gives for figuring out what actual frequency will result from the divider FREQ in (for example) SOUND 0,FREQ,tone,volume is as follows:

This means that, at values for FREQ around 85 (the middle of the Atari's frequency range), the minimum actual frequency step is about 4 Hz. While adequate for solo parts, this kind of frequency resolution can really grate on your ears when there are three or four voices active. To illustrate the real meaning of this, try the following one-liner:

FOR F = 255 TO 0 STEP -1 : SOUND 0,F,10,15 : NEXT F

The resultant sound is a smooth glide until you get near the top end, when you begin to really hear the steps.

For those of you with a keen ear and/or a strong sense of music, cheer up. Atari, once again, gave us a solution. The entire Atari audio system is controlled by hardware register AUDCTL ($D208). Normally, the audio channels are clocked by an oscillator running at 63921 Hz. But, the user may specify that channels zero and two (which Atari calls one and three in the Technical Manual… oh well) are to be clocked by a 1,789,790 Hz oscillator. If you change 63921 to 1789790 in the formula above and plug in 255 (the highest value) for FREQ, you will see that the lowest note thus playable is around 3000 Hz!

But we have yet another solution available via AUDCTL: instead of an 8-bit counter for a single audio channel, we use a pair of channels to produce a 16 bit counter. (Unfortunately, we then are limited to two sound channels.) The modified formula then becomes:

Since FREQ now has values from 0 to 65535, it's obvious we have many more actual frequencies available to us. I present herewith two sample programs using this technique. Program 2 shows two voices doing very smooth glides. Program 3 shows a 9-octave chromatic scale (C, C#, D, D#, etc.). This compares to the 3-octave scale available via the standard SOUND commands.

Finally, if you simply need lower notes than you can get with SOUND, TRY POKEing AUDCTL to one. This has the effect of lowering all notes by approximately two octaves. Unfortunately, you do not get to have some channels high and others low. Example:

Program 1.

equates and commentary
0000		1000	    .PAGE "equates and commentary"
		1010 ;
		1020 ; STEAL A RESET
		1030 ;
		1040 ; listing for COMPUTE! magazine, April, 1982
		1050 ;
		1060 ;
		1070 ; there are two parts to this listing:
		1080 ;   1. what happens when this is first loaded
		1090 ;      (which initializes everything)
		1100 ;   2. what happens when the User pushes
		1110 ;      SYSTEM RESET.
		1120 ;
		1130 ; Most of 1 is understandable.  Most of 2
		1140 ; is magic.  If it works, don't knock it.
		1150 ;
02E7		1160 MEMLO  =    $2E7       ; BOTTOM OF USABLE MEM
		1170 ;
00FF		1180 LOW    =    $FF
0100		1190 HIGH   =    $100
		1200 ;
		1210 ; EQUATES INTO BASIC ROM
		1220 ;
BD72		1230 SETDZ  =	 $BD72      ; ENSURE OUTPUT TO CONSOLE
0092		1240 MEOL   =	 $92	    ;FLAG: LINE MODIFIED
BD99		1250 FIXEOL =	 $BD99	    ; UNMODIFY
00B9		1260 ERRNUM =	 $B9	    ; AT LEAST BASIC THINKS SO
B940		1270 ERROR  =	 $B940	    ; HANDLE ERRORS
00BD		1280 TRAPFLAG =	 $BD
DA51		1290 INITBUF =	 $DA51	    ;SAFETY
0011		1300 BRKFLAG =   $11
BD41		1310 CLOSEALL =  $BD41	    ;close IOCBs 1-7
000C		1320 DOSINIT =   $0C        ;see article
		1330 ;
SETUP THE RESET VECTOR
0000		1340	    .PAGE "SETUP THE RESET VECTOR"
		1350 ;
		1360 ; We move the OS DOSINIT vector to point to outselves
		1370 ;
		1380 ; ***** NOTE: change next line to suit!!! *****
0000		1390        *=	 $1F00
		1400 CHANGEDOSINIT
1F00 A50C	1410	    LDA  DOSINIT
1F02 8D231F	1420	    STA  RESET + 1
1F05 A50D	1430        LDA  DOSINIT + 1 ; Self modifying code…nasty
1F07 8D241F	1440        STA  RESET + 2
1F0A A922	1450        LDA  #RESET&LOW
1F0C 850C	1460        STA  DOSINIT1F0E A91F       1470        LDA  #RESET/HIGH
1F10 850D       1480        STA  DOSINIT+1 ; We have chanqed the pointer
                1490 ;
                1500 ; Here we Move MEMLO…
                1510 ;   we arbitrarily use 256 bytes of space
                1520 ;
                1530 MOVEMEMLO
1F12 A900       1540        LDA  #RESETTOP&LOW
1F14 8DE702     1550        STA  MEMLO
1F17 A920       1560        LDA  #RESETTOP/HIGH
1F19 8DE802     1570        STA  MEMLO+1
1F1C 60         1580        RTS
                1590 ;
                1600 ; FROMBASIC  is just a second entry
                1610 ;   entry point into the initialization…
                1620 ;   for initializing from BASIC
                1630 ;
                1640 FROMBASIC
1F1D 68         1650        PLA            ; get cnt of parms off stack
1F1E F0E0       1660        BEQ  CHANGEDOSINIT ; good…no parms
1F20 D0FE       1670 OOPS   BNE  OOPS      ; otherwise, tough!
THE ACTUAL RESET TRAP
1F22            1680        .PAGE "THE ACTUAL RESET TRAP"
                1690 ;
                1700 ; On reset, DOS normally gets
                1710 ; called to reinitialize itself …
                1720 ; we use this to our advantage by
                1730 ; reinit'ing both DOS and BASIC
                1740 ;
                1750 RESET
1F22 200000     1760        JSR 0          ; second two bytes get replaced
                1770 ;                   by the address of real DOSINIT
1F25 A2FF       1780        LDX #$FF
1F27 9A         1790        TXS            ; BASIC likes it this way
1F28 8611       1800        STX BRKFLAG    ; ensure no BREAK key pending
1F2A 2041BD     1810        JSR CLOSEALL   ; so everybody agrees
1F2D 2072DB     1820        JSR SETDZ
1F30 A592       1830        LDA MEOL
1F32 F003       1840        BEQ RST2
1F34 2099BD     1850        JSR FIXEOL
                1860 RST2
1F37 2051DA     1870        JSR INITBUF
		1880 ;
1F3A 20121F     1890        JSR MOVEMEMLO  ; to protect this code
                1900;
                1910 ; NOW we fool BASIC into thinking
                1920 ; an error occured
                1930 ;
                1940 ;
1F3D A9FF       1950        LDA  #255      ; (or your choice of errors)
1F3F 85B9       1960        STA  ERRNUM
1F41 4C40B9     1970        JMP  ERROR     ; do it
                1980 ;
                1990 ; THE FOLLOWING EQUATE IS USED TO SET                2000 ; RESETTOP on a page boundary
                2010 ;
2000            2020 RESETTOP =  x + $FF&*FF00
                2030 ; SET UP LOAD AND GO
                2040 ;
lF44            2050        x = $2E0
02E0 001F       2060         .WORD CHANGEDOSINIT
02E2            2070         .END
THE ACTUAL     RESET TRAP
= 02E7 MEMLO         =00FF LOW           = 0100 HIGH        =BD72 SETDZ
= 0092 MEOL          =BD99 FIXEOL        = 00B9 ERRNUM      =B940 ERROR
= 00BD TRAPFLAG      =DA51 INITEUF       = 0011 BRKFLAG     =BD41 CLOSEALL
= 000C DOSZNIT       1F00 CHANGEDOSINIT    1F22 RESET        1F12 MOVEMEMLO
= 2000 RESETTOP      1F1D FROMBASIC        1F20 OOPS         1F37 RST2

Program 2.

10 AUDCTL = 53768:DBL = 120
20 AUDF1 = 53760 : AUDC1 = 53761
30 SOUND   l,10, 10, 15 : SOUND 3, 10, 10, 15
40 POKE AUDC1, 0 : POKE AUDC1 + 4, 0
50 POKE AUDCTL, DBL
60 FOR J = 10 TO 15 : POKE AUDF1 + 2, J: POKE AUDF1 + 6, 20-J
70 FOR I = 0 TO 255 : POKE AUDF1, I: POKE AUDF1 + 4, 255-I: NEXT I
80 NEXT J
     …VERY SMOOTH GLIDES…

Program 3.

10 AUDCTL = 53768 : DBL = 120
12 0SC = 1789790/2
20 AUDF1 = 53760 : AUDC1 = 53761
30 SOUND 1, 10, 10, 0
40 POKE AUDC1, 0 : POKE AUDC1 + 4, 0
50 POKE AUDCTL, DBL
60 P2 = 2^(1/12)
70 NTE = 16 : REM C IN THE REAL BASS
80 FOR I = 1 TO 109
90 FREQ = INT (OSC/NIE-7 + 0.5) :F0 = INT (FREQ/256)
92 Fl = FREQ--256*F0
100 POKE AUDF1, F1 : POKE AUDF1 + 2, F0
102 POKE AUDC1 + 2, 175
103 PRINT "NOW PLAYING "; INT (NTE + 0.5);" HZ"
105 FOR J = 1 TO 100 : NEXT J
110 NTE = NTE*P2
120 NEXT I
130 GOTO 70
 …9 OCTAVE CHROMATIC SCALE…
SOUND 0, 60, 10, 12 : SOUND 1, 45, 10, 12 : POKE
    53768, 1 : FOR I = 1 TO 500 : NEXT I

Again, investigate De Re for even more details on some of the more complex aspects of the sound system. (Want to hear your Atari "MOO" like a cow?)

A Final Caution

I have had a couple of people write or call me complaining that, when they tried my assembly language routines, they didn't work. Honest, they do work. The listings published in the magazine are the ones I actually used. Sometimes the problem simply resolves to a typo on the part of the user. But sometimes it turns out to be an address conflict.

I do most of my work with OS/A+ and BASIC A+ (naturally. But I use Atari BASIC to check out programs for these pages), and I usually use memory addresses which are convenient to me. Since I get tired of putting everything in page six, I sometimes use $1F00 or some such as an origin. You can use these addresses in your system with the Atari Assembler/Editor if you change MEMLO to, say, $3000 (my usual choice, and achievable with the LOMEM command of EASMD). However, it may be more convenient to use SIZE to look at where your source, etc. is and then change the origin to reflect your memory configuration.

Of course, you can always assemble into the dreaded page six or assemble directly to disk (or cassette). But, in any case, don't use an origin ("* = xxxx") which conflicts with SIZE in your system. I purposely give you source listings so that you can see how things work; take the time to type them in and it will probably prove easier in the long run.