Chapter Ten


GRAPHICS

    One of the most exciting and unique features of the ATARI computers is their excellent graphics. When compared with other popular microcomputers for quality of graphics, the ATARI is generally the clear winner. In fact, most arcade-type games available for several different computers look best on the ATARI, and advertising generally utilizes photographs taken from the screen generated on an ATARI.

    "But," you say, "that's only available from BASIC." There is a common misconception among ATARI owners that the graphics commands are in the BASIC cartridge, and that commands like PLOT and DRAWTO can't be used without the BASIC cartridge in place. In fact, all of the graphics routines are located in the OS, and are therefore available from any language. We'll now see how to use these routines from assembly language.

    Any program which requires such commands as GRAPHICS n, PLOT, or the other graphic commands, generally utilizes these many times throughout the program. It is therefore easiest to present these routines as a set of assembly language subroutines, which can be called from any program. These routines can be saved on a disk as a group and ENTERed into any program requiring graphics routines. To utilize the routines in the program, you'll generally have to load the X and Y registers and the accumulator with parameters that you'd like to implement, and then JSR to the appropriate routine. Note that this parameter passing is discussed in the comments to each routine, to make its use clear. Detailed discussion of the subroutines appears in the section following the program listings.


THE ASSEMBLY LANGUAGE GRAPHICS SUBROUTINES

Listing 10.1

            0100 ; ******************************
            0110 ; CIO equates
            0120 ; ******************************
0340        0130 ICHID =    $0340
0341        0140 ICDNO =    $0341
0342        0150 ICCOM =    $0342
0343        0160 ICSTA =    $0343
0344        0170 ICBAL =    $0344
0345        0180 ICBAH =    $0345
0346        0190 ICPTL =    $0346
0347        0200 ICPTH =    $0347
0348        0210 ICBLL =    $0348
0349        0220 ICBLH =    $0349
034A        0230 ICAX1 =    $034A
034B        0240 ICAX2 =    $034B
E456        0250 CIOV  =    $E456
            0260 ; ******************************
            0270 ; Other equates needed
            0280 ; ******************************
02C4        0290 COLOR0 =   $02C4
0055        0300 COLCRS =   $55
0054        0310 ROWCRS =   $54
02FB        0320 ATACHR =   $02FB
00CC        0330 STORE1 =   $CC
00CD        0340 STOCOL =   $CD
0000        0350        *=  $600
            0360 ; ******************************
            0370 ; The SETCOLOR routine
            0380 ; ******************************
            0390 ; Before calling this routine,
            0400 ; the registers should be set
            0410 ; just like the BASIC SETCOLOR:
            0420 ; SETCOLOR color,hue,luminance
            0430 ;    stored respectively in
            0440 ;   X reg.,accumulator,Y reg.
            0450 SETCOL
0600 0A     0460        ASL A         ; Need to multiply
0601 0A     0470        ASL A         ; hue by 16, and
0602 0A     0480        ASL A         ; add it to lumimance.
0603 0A     0490        ASL A         ; Now hue is * 16
0604 85CC   0500        STA STORE1    ; temporarily
0606 98     0510        TYA           ; So we can add
0607 18     0520        CLC           ; Before adding
0608 65CC   0530        ADC STORE1    ; Now have sum
060A 9DC402 0540        STA COLOR0,X  ; Actual SETCOLOR
060D 60     0550        RTS           ; All done
            0560 ; ******************************
            0570 ; The COLOR command
            0580 ; ******************************
            0590 ; For these routines, we will
            0600 ; simply store the current COLOR
            0610 ; in STOCOL, so the COLOR
            0620 ; command simply requires that
            0630 ; the accumulator hold the value
            0640 ; "n" in the command COLOR n
            0650 COLOR
060E 85CD   0660        STA STOCOL    ; That's it!
0610 60     0670        RTS           ; All done
            0680 ; ******************************
            6900 ; The GRAPHICS command
            0700 ; ******************************
            0710 ; The "n" parameter of
            0720 ; a GRAPHICS n command will be
            0730 ; passed to this routine in the
            0740 ; accumulator
            0750 GRAFIC
0611 48     0760        PHA           ; Store on stack
0612 A260   0770        LDX #$60      ; IOCB6 for screen
0614 A90C   0780        LDA #$C       ; CLOSE command
0616 9D4203 0790        STA ICCOM,X   ; in command byte
0619 2056E4 0800        JSR CIOV      ; Do the CLOSE
061C A260   0810        LDX #$60      ; The screen again
061E A903   0820        LDA #3        ; OPEN command
0620 9D4203 0830        STA ICCOM,X   ; in command byte
0623 A9AD   0840        LDA #NAME&255 ; Name is "S:"
0625 9D4403 0850        STA ICBAL,X   ; Low byte
0628 A906   0860        LDA #NAME/256 ; High byte
062A 9D4503 0870        STA ICBAH,X
062D 68     0880        PLA           ; Get GRAPHICS n
062E 9D4B03 0890        STA ICAX2,X   ; Graphics mode
0631 29F0   0900        AND #$F0      ; Get high 4 bits
0633 4910   0910        EOR #$10      ; Flip high bit
0635 090C   0920        ORA #$C       ; Read or write
0637 9D4A03 0930        STA ICAX1,X   ; n+16, n+32 etc.
063A 2056E4 0940        JSR CIOV      ; Setup GRAPHICS n
063D 60     0950        RTS           ; All done
            0960 ; ******************************
            0970 ; The POSITION command
            0980 ; ******************************
            0990 ; Identical to the BASIC
            1000 ; POSITION X,Y command.
            1010 ; Since X may be greater than
            1020 ; 255 in GRAPHICS 8, we need to
            1030 ; use the accumulator for the
            1040 ; high byte of X.
            1050 POSITN
063E 8655   1060        STX COLCRS    ; Low byte of X
0640 8556   1070        STA COLCRS+1  ; High byte of X
0642 8454   1080        STY ROWCRS    ; Y position
0644 60     1090        RTS           ; All done
            1100 ; ******************************
            1110 ; The PLOT command
            1120 ; ******************************
            1130 ; We'll use the X,Y, and A just
            1140 ; like in the POSITION command.
            1150 PLOT
0645 203E06 1160        JSR POSITN    ; To store info
0648 A260   1170        LDX #$60      ; For the screen
064A A90B   1180        LDA #$B       ; Put record
064C 9D4203 1190        STA ICCOM,X   ; Command byte
064F A900   1200        LDA #0        ; Special case of
0651 9D4803 1210        STA ICBLL,X   ;  I/O using the
0654 9D4903 1220        STA ICBLH,X   ;  accumulator
0657 A5CD   1230        LDA STOCOL    ; Get COLOR to use
0659 2056E4 1240        JSR CIOV      ; Plot the point
065C 60     1250        RTS           ; All done
            1260 ; ******************************
            1270 ; The DRAWTO command
            1280 ; ******************************
            1290 ; We'll use the X,Y, and A just
            1300 ; like in the POSITION command
            1310 DRAWTO
065D 203E06 1320        JSR POSITN    ; To store info
0660 A5CD   1330        LDA STOCOL    ; Get COLOR
0662 8DFB02 1340        STA ATACHR    ; Keep CIO happy
0665 A260   1350        LDX #$60      ; The screen again
0667 A911   1360        LDA #$11      ; For DRAWTO
0669 9D4203 1370        STA ICCOM,X   ; Command byte
066C A90C   1380        LDA #$C       ; As in XIO
066E 9D4A03 1390        STA ICAX1,X   ; Auxiliary 1
0671 A900   1400        LDA #0        ; Clear
0673 9D4B03 1410        STA ICAX2,X   ; Auxiliary 2
0676 2056E4 1420        JSR CIOV      ; Draw the line
0679 60     1430        RTS           ; All done
            1440 ; ******************************
            1450 ; The FILL command
            1460 ; ******************************
            1470 ; We'll use the X,Y, and A just
            1480 ; like in the POSITION command.
            1490 ; This is similar to DRAWTO
            1500 FILL
067A 203E06 1510        JSR POSITN    ; To store info
067D A5CD   1520        LDA STOCOL    ; Get COLOR
067F 8DFB02 1530        STA ATACHR    ; Keep CIO happy
0682 A260   1540        LDX #$60      ; The screen again
0684 A912   1550        LDA #$12      ; For FILL
0686 9D4203 1560        STA ICCOM,X   ; Command byte
0689 A90C   1570        LDA #$C       ; As in XIO
068B 9D4A03 1580        STA ICAX1,X   ; Auxiliary 1
068E A900   1590        LDA #0        ; Clear
0690 9D4B03 1600        STA ICAX2,X   ; Auxiliary 2
0693 2056E4 1610        JSR CIOV      ; FILL the area
0696 60     1620        RTS           ; All done
            1630 ; ******************************
            1640 ; The LOCATE command
            1650 ; ******************************
            1660 ; We'll use the X,Y, and A just
            1670 ; like in the POSITION command
            1680 ; and the accumulator will
            1690 ; contain the LOCATEd color
            1700 LOCATE
0697 203E06 1710        JSR POSITN    ; To store info
069A A260   1720        LDX #$60      ; The screen again
069C A907   1730        LDA #7        ; Get record
069E 9D4203 1740        STA ICCOM,X   ; Command byte
06A1 A900   1750        LDA #0        ; Special case of
06A3 9D4803 1760        STA ICBLL,X   ; data transfer
06A6 9D4903 1770        STA ICBLH,X   ; in accumulator
06A9 2056E4 1780        JSR CIOV      ; Do the LOCATE
06AC 60     1790        RTS           ; All done
            1800 ; ******************************
            1810 ; The screen's name
            1820 ; ******************************
06AD 53     1830 NAME   .BYTE "S:",$9B
06AE 3A
06AF 9B


DISCUSSION OF THE GRAPHICS SUBROUTINES

    The first point to note about these routines is that they simply use the standard CIO equates, which we have seen so often before, plus six new ones. We don't need a whole new set of equates, since we're using the standard ATARI CIO routines for all of the graphics commands. Of the six new equates, two are simply storage locations: STOCOL is used to store the COLOR information used in several of the routines, and STORE1 is used for temporary storage of information. These are arbitrarily located at $CD and $CC respectively, but you may feel free to locate them at any safe memory location you choose. One such place would be $100 and $101, which are the bottom two locations of the stack. Another of the new equates is COLOR0, which is the first of the 5 locations used to store color information in the ATARI computers, found in decimal locations 708 to 712. The second is COLCRS, a 2-byte storage location at $55 and $56, which always stores the current column location of the cursor. Since in GRAPHICS 8 there are 320 possible horizontal locations, and we know that each single byte can store only 256 possible values, we need 2 bytes to store all possible horizontal positions of the cursor. However, in all graphics modes other than GRAPHICS 8, it is obvious that location $56 will always be equal to zero. The third new equate is ROWCRS, location $54, which simply keeps track of the vertical position of the cursor. No graphics mode has more than 192 possible vertical positions of the cursor, so only 1 byte is required to store this information. The final new equate is ATACHR, location $2FB, which is used to store the color of the line being drawn in both the FILL and DRAWTO routines.

    These routines have been assembled using an origin of $600 for convenience. If you plan to use these in a larger assembly language program, just renumber these subroutines to some high line numbers, such as 25000 and up, and merge these routines with your program before assembling it. This way, you'll have all of the normal graphics commands available from assembly language, without needing to laboriously enter them into each program you write.

    The first routine is the assembly language equivalent of the BASIC command SETCOLOR. We know that this is the standard form of the command in BASIC:

SETCOLOR color register, hue, luminance

In the assembly language subroutine, we first need to load the 6502 registers with the equivalent information. A typical calling routine to use this subroutine to simulate the BASIC command

SETCOLOR 2,4,10

would be as follows:

25 LDX #2
30 LDA #4
35 LDY #10
40 JSR SETCOL

We use the 6-letter form of the name SETCOL for SETCOLOR so that this routine will be compatible with all of the available assemblers for the ATARI. If the assembler you are using allows label names longer than 6 characters, feel free to use the whole routine name. This same convention will be used for all of the graphics routines – for instance, POSITN for POSITION.

    To perform the SETCOLOR command, we need to add the luminance to 16 times the hue and store the result into the appropriate color register. To multiply the hue by 16, we'll simply use the accumulator and perform four ASL A instructions. Since each doubles the value contained in the accumulator, the result is 16 times the initial value. After the multiplication, we'll store the result into our temporary storage location and get the luminance into the accumulator with a TYA instruction, setting up for the addition. We then clear the carry bit, as usual prior to addition, and add the result of our previous multiplication to the luminance. Finally, we use the value in the X register, which is the color register desired, as an index into the five color register locations described above. Since we want to SETCOLOR 2 in this example, we loaded the X register with 2 before the call to the subroutine, and the color information is stored in $2C6.

    The next routine, the COLOR command, is by far the easiest of all the routines. To call the COLOR equivalent of the BASIC command

COLOR 3

we simply need the following assembly language code:

25 LDA #3
30 JSR COLOR

The routine simply stores the color selected into our storage location for color, STOCOL, where it will be available for the other graphics routines which require it.

    The GRAPHICS command is implemented similiarly. To mimic the BASIC command

GRAPHICS 23

we simply use the following assembly language code:

25 LDA #23
30 JSR GRAFIC

The first thing we need to do is store the graphics mode required. We could store it in STORE1, but pushing it onto the stack is quicker in this case; we don't need to do addition or multiplication, as we did in SETCOLOR. The next four lines of code simply close the screen as a device. This is for insurance. If the screen is already closed, we haven't hurt anything. However, if it's open and we try to reopen it, we'll get an error, so we close it first for insurance. Note that simply by using IOCB6 (loading the X register with $60), we specify the screen, using the default device number assigned by ATARI.

    The remainder of the GRAPHICS command simply opens the screen in the particular graphics mode we desire. We again use IOCB6, storing the OPEN command in the command byte of the IOCB in line 830. The name of the screen is S:, and we load the address of this name into ICBAL and ICBAH. The graphics mode is then retrieved from the stack and stored in the second auxiliary byte. The only important bits of the graphics mode in ICAX2 are the lower 4 bits, which specify the graphics mode itself; in this case, GRAPHICS 7. The upper 4 bits control the clearing of the screen, the presence of the text window, and so on, as described in Chapter 8. In this case, we have added 16 to the graphics mode, to eliminate the text window. To isolate these bits, we AND the graphics mode with $F0, which yields the high nibble of the graphics mode. The OS requires that the high bit of this information be inverted, so next we EOR this nibble with $10, to flip the high bit. Finally, we set the low nibble of this byte to $C, to allow either reading or writing to the screen, and we store the byte in ICAX1 of the IOCB. The call to CIO completes our graphics routine and sets up the screen as we had wanted.

    As we have already discussed, the POSITION command for GRAPHICS 8 requires 320 possible X locations, so we need 2 bytes to hold this large a number. Therefore, to simulate the command

POSITION 285,73

we will store the low byte of the X coordinate in the X register, the high byte in the accumulator, and the Y coordinate in the Y register, as follows:

25 LDX #30
30 LDA #1
35 LDY #73
40 JSR POSITN

Obviously, in any graphics mode other than GRAPHICS 8, the accumulator is always loaded with a zero prior to calling POSITN, and the X register simply contains the X coordinate. The routine itself simply stores the appropriate information into the required locations. The X coordinate is stored into COLCRS and COLCRS+1, and the Y coordinate is stored into ROWCRS.

    The PLOT command of

PLOT 258,13

in BASIC is simulated by the following code in assembly language:

25 LDX #3
30 LDA #1
35 LDY #13
40 JSR PLOT

This uses the same convention as the POSITN command above. In fact, the PLOT routine begins with a JSR POSITN, which stores the information passed to the routine into the correct locations for use by the OS following the call to CIO. Since we want to output to the screen, we use IOCB6, and the command byte is $B for put record. In this case, we simply want to output a single byte of information, so we use the special case of accumulator I/O accessed by setting the length of the output buffer to zero. Then we load the accumulator with the color information we want to plot, and the call to CIO plots the point for us.

    The routines to DRAWTO and FILL are so similar that they will be discussed together. The calling sequence is identical to the PLOT and POSITION commands, so to mimic the BASIC command

DRAWTO 42,80

we use the sequence

25 LDX #42
30 LDA #0
35 LDY #80
40 JSR DRAWTO

To use the FILL command, simply change line 40 to JSR FILL.

    The routine begins with a call to the POSITN routine to store the required information. The color information is then stored in ATACHR, and we use IOCB6 again, loading ICCOM with $11 for DRAWTO and with $12 for FILL. ICAX1 needs a $C, and we clear ICAX2 before completing the routine by calling CIO. These routines are absolutely analogous to the respective BASIC XIO commands which accomplish the same ends. For instance, to draw a line, we can use this command:

XIO 17,#6,12,0,"S:"

In this command, the 17 is the $11 command byte, the #6 is the IOCB number, the 12 is stored in ICAX1, the zero in ICAX2, and the device name is S:. Again, exactly the same XIO command can be use to FILL an area, by simply changing the 17 to 18 ($12).

    The final routine, the LOCATE command, is virtually identical to the PLOT command, except that we use the get record command, rather than the put record command. The same use is made of the special single-byte accumulator I/O mode, by setting both ICBLL and ICBLH to zero. The calling routine to duplicate the BASIC command

LOCATE 10,12,A

is as follows:

25 LDX #10
30 LDA #0
35 LDY #12
40 JSR LOCATE

In this case, the accumulator will contain the color value found at the coordinates 10,12 following the call to the LOCATE routine, so a STA command could save this information, or it could be used immediately, by comparing it to some desired value, or in other ways.

    This concludes the discussion of the assembly language counterparts to the BASIC graphics commands. Use them in some simple programs, and you'll see how soon they become familiar and how easy they are. In fact, they're almost as easy to use as the BASIC commands. However, since both BASIC and assembly language use the same OS routines to accomplish such operations as DRAWTO and FILL, don't expect that the assembly language routines will be much faster than the BASIC routines you are used to. They will be slightly faster, since you don't have to pay the overhead that BASIC requires in terms of time, but you will experience nowhere near the difference in speed that you have now come to expect when converting from BASIC to assembly language programming. To accomplish this kind of speedup, you'll have to write your own DRAWTO and FILL routines, using a totally different logic from that used by the ATARI OS. Such routines have been written and are much faster than the OS routines, but they are not in the public domain, and you'll have to write your own if speed is critical.

    Now that you are becoming proficient in assembly language, you may want to change the ATARI central routines for your own purposes. If you want to try this, purchasing the OS listings and Technical User's Notes from ATARI is highly recommended. You can then look at the commented source code for the OS routines, and modify them for your own routines. Just include them as part of your own programs, making the modifications you would like. However, remember that the code for the OS belongs to ATARI. You can use such modifications in programs for your own use, but be sure to get permission from ATARI before trying to offer for sale any programs containing parts of ATARI's OS. One easy change to try is to allow plotting and drawing without checking for cursor out of range, which slows things down quite a bit. Just be sure that your program calculates the values correctly, or else…

    Remember that anything possible from BASIC is also possible from assembly language. One frequently used example of this is animation by means of rotation of the color registers, possible using either the special GTIA modes, or the regular graphics modes. A very simple routine can rotate the standard color registers virtually instantaneously:

15 LDA $708
20 STA STOCOL
25 LDA $709
30 STA $708
35 LDA $710
40 STA $709
45 LDA $711
50 STA $710
55 LDA $712
60 STA $711
65 LDA STOCOL
70 STA $712

Now that you can draw detailed graphics from assembly language programs, this trick can be used to animate pictures with virtually no slowdown in program execution. For instance, implementation of a down-the-trench type game is simple, by letting rotation of the colors give the illusion of motion down the trench.


PLAYER-MISSILE GRAPHICS FROM ASSEMBLY LANGUAGE

    Another exciting feature of the ATARI computers is player-missile graphics. We've already seen an example using an assembly language subroutine to move a player. But in that program, the entire setup for player-missile graphics was in BASIC, and only the routine to move the player was in assembly language. To show how to perform these same operations in a purely assembly language program, this BASIC program has been totally translated into assembly language and is presented below. In this program, decimal addresses are used for the most part, since that is the way the BASIC program was written; this assembly language program is as similiar to that BASIC program as is feasible.

Listing 10.2

            0100 ; ******************************
            0110 ; CIO equates
            0120 ; ******************************
0340        0130 ICHID =    $0340
0341        0140 ICDNO =    $0341
0342        0150 ICCOM =    $0342
0343        0160 ICSTA =    $0343
0344        0170 ICBAL =    $0344
0345        0180 ICBAH =    $0345
0346        0190 ICPTL =    $0346
0347        0200 ICPTH =    $0347
0348        0210 ICBLL =    $0348
0349        0220 ICBLH =    $0349
034A        0230 ICAX1 =    $034A
034B        0240 ICAX2 =    $034B
E456        0250 CIOV  =    $E456
            0260 ; ******************************
            0270 ; Other equates needed
            0280 ; ******************************
00CC        0290 YLOC   =   $CC       ; Indirect address for Y
00CE        0300 XLOC   =   $CE       ; To remember X position
00D0        0310 INITX  =   $D0       ; Initial X value
00D1        0320 INITY  =   $D1       ; Initial Y value
0100        0330 STOTOP =   $100      ; Temporary storage (ADRESS)
D300        0340 STICK  =   $D300     ; PORTA - Hardware STICK(0) location
D000        0350 HPOSP0 =   $D000     ; Horizontal position Player 0
0000        0360        *=  $600
            0370 ; ******************************
            0380 ; First, lower top of RAM
            0390 ; ******************************
0600 A56A   0400        LDA 106       ; Get top of RAM
0602 8D0001 0410        STA STOTOP    ; Temporary storage
0605 38     0420        SEC           ; Setup for subtract
0606 E908   0430        SBC #8        ; Save 8 pages for PMG
0608 856A   0440        STA 106       ; Tell Atari - new RAMTOP
060A 8D07D4 0450        STA 54279     ; PMBASE
060D 85CF   0460        STA XLOC+1    ; To erase PM RAM
060F A900   0470        LDA #0        ; put indirect
0611 85CE   0480        STA XLOC      ; address here.
            0490 ; ******************************
            0500 ; Next, reset GRAPHICS 0
            0510 ; ******************************
0613 A900   0520        LDA #0        ; GRAPHICS 0
0615 48     0530        PHA           ; Store on stack
0616 A260   0540        LDX #$60      ; IOCB6 for screen
0618 A90C   0550        LDA #$C       ; CLOSE command
061A 9D4203 0560        STA ICCOM,X   ; in command byte
061D 2056E4 0570        JSR CIOV      ; Do the CLOSE
0620 A260   0580        LDX #$60      ; The screen again
0622 A903   0590        LDA #3        ; OPEN command
0624 9D4203 0600        STA ICCOM,X   ; in command byte
0627 A9ED   0610        LDA #NAME&255 ; Name is "S:"
0629 9D4403 0620        STA ICBAL,X   ;  Low byte
062C A906   0630        LDA #NAME/256 ;  High byte
062E 9D4503 0640        STA ICBAH,X
0631 68     0650        PLA           ; Get GRAPHICS 0
0632 9D4B03 0660        STA ICAX2,X   ; Graphics mode
0635 29F0   0670        AND #$F0      ; Get high 4 bits
0637 4910   0680        EOR #$10      ; Flip high bit
0639 090C   0690        ORA #$C       ; Read or write
063B 9D4A03 0700        STA ICAX1,X   ; n+16, n+32 etc.
063E 2056E4 0710        JSR CIOV      ; Set up GRAPHICS 0
            0720 ; ******************************
            0730 ; Now set up Player/Missile Graphics
            0740 ; ******************************
0641 A978   0750        LDA #120      ; Initial X value
0643 85D0   0760        STA INITX     ; Put in place
0645 A932   0770        LDA #50       ; Initial Y value
0647 85D1   0780        STA INITY     ; Put in place
0649 A92E   0790        LDA #46       ; Double line
064B 8D2F02 0800        STA 559       ; resolution - SDMCTL
            0810 ; ******************************
            0820 ; Now clear out PM area of RAM
            0830 ; ******************************
064E A000   0840        LDY #0        ; Use as counter
            0850 CLEAR
0650 A900   0860        LDA #0        ; Byte to be stored
0652 91CE   0870        STA (XLOC),Y  ; Clear 1st byte
0654 88     0880        DEY           ; Is page finished?
0655 D0FB   0890        BNE CLEAR     ; Page not done yet
0657 E6CF   0900        INC XLOC+1    ; Page is done
0659 A5CF   0910        LDA XLOC+1    ; On to next page
065B CD0001 0920        CMP STOTOP    ; Are we done?
065E F0F2   0930        BEQ CLEAR     ; One more page
0660 90F0   0940        BCC CLEAR     ; Keep going
            0950 ; ******************************
            0960 ; Now we'll insert the player into
            0970 ; the appropriate place in the
            0980 ; PMG RAM area
            0990 ; ******************************
0662 A56A   1000        LDA 106       ; First, calculate
0664 18     1010        CLC           ; correct Y position.
0665 6902   1020        ADC #2        ; PMBASE+512 (2 pages)
0667 85CD   1030        STA YLOC+1    ; High byte of YLOC
0669 A5D1   1040        LDA INITY     ; Add Y screen coordinate
066B 85CC   1050        STA YLOC      ; For low byte
066D A000   1060        LDY #0        ; As a counter
            1070 INSERT
066F B9F006 1080        LDA PLAYER,Y  ; Get byte of player
0672 91CC   1090        STA (YLOC),Y  ; Put it in place
0674 C8     1100        INY           ; For next byte
0675 C008   1110        CPY #8        ; Are we done?
0677 D0F6   1120        BNE INSERT    ; No
0679 A5D0   1130        LDA INITX     ; Get initial X
067B 8D00D0 1140        STA HPOSP0    ; Tell Atari
067E 85CE   1150        STA XLOC      ; Also here
0680 A944   1160        LDA #68       ; Make player red
0682 8DC002 1170        STA 704       ; as in BASIC - PCOLR0
0685 A903   1180        LDA #3        ; To enable Player
0687 8D1DD0 1190        STA 53277     ;  Missle Graphics - GRACTL
            1200 ; ******************************
            1210 ; The main loop - very short
            1220 ; ******************************
            1230 MAIN
068A 209A06 1240        JSR RDSTK     ; Read stick - move player
068D A205   1250        LDX #5        ; To control the
068F A000   1260        LDY #0        ; player, we
            1270 DELAY
0691 88     1280        DEY           ; have to add
0692 D0FD   1290        BNE DELAY     ; a delay - this
0694 CA     1300        DEX           ; routine slows
0695 D0FA   1310        BNE DELAY     ; things down.
0697 4C8A06 1320        JMP MAIN      ; And do it again
            1330 ; ******************************
            1340 ; Now read the joystick #1
            1350 ; ******************************
            1360 RDSTK
069A AD00D3 1370        LDA STICK     ; Get joystick value
069D 2901   1380        AND #1        ; Is bit 0 = 1?
069F F016   1390        BEQ UP        ; No - 11, 12 or 1 o'clock
06A1 AD00D3 1400        LDA STICK     ; Get it again
06A4 2902   1410        AND #2        ; Is bit 1 = 1?
06A6 F020   1420        BEQ DOWN      ; No - 5, 6 or 7 o'clock
06A8 AD00D3 1430 SIDE   LDA STICK     ; Get it again
06AB 2904   1440        AND #4        ; Is bit 3 = 1?
06AD F02E   1450        BEQ LEFT      ; No - 8, 9 or 10 o'clock
06AF AD00D3 1460        LDA STICK     ; Get it again
06B2 2908   1470        AND #8        ; Is bit 4 = 1?
06B4 F02F   1480        BEQ RIGHT     ; No - 2, 3 or 4 o'clock
06B6 60     1490        RTS           ; Joystick straight up
            1500 ; ******************************
            1510 ; Now move player appropriately,
            1520 ; starting with upward movement.
            1530 ; ******************************
06B7 A001   1540 UP     LDY #1        ; Setup for moving byte 1
06B9 C6CC   1550        DEC YLOC      ; Now 1 less than YLOC
06BB B1CC   1560 UP1    LDA (YLOC),Y  ; Get 1st byte
06BD 88     1570        DEY           ; To move it up one position
06BE 91CC   1580        STA (YLOC),Y  ; Move it
06C0 C8     1590        INY           ; Now original value
06C1 C8     1600        INY           ; Now set for next byte
06C2 C00A   1610        CPY #10       ; Are we done?
06C4 90F5   1620        BCC UP1       ; No
06C6 B0E0   1630        BCS SIDE      ; Forced branch!!!
            1640 ; ******************************
            1650 ; Now move player down
            1660 ; ******************************
06C8 A007   1670 DOWN   LDY #7       ; Move top byte first
06CA B1CC   1680 DOWN1  LDA (YLOC),Y ; Get top byte
06CC C8     1690        INY          ; to move it down screen
06CD 91CC   1700        STA (YLOC),Y ; Move it
06CF 88     1710        DEY          ; Now back to starting value
06D0 88     1720        DEY          ; Set for next lower byte
06D1 10F7   1730        BPL DOWN1    ; If Y >= 0 keep going
06D3 C8     1740        INY          ; Set to zero
06D4 A900   1750        LDA #0       ; to clear top byte
06D6 91CC   1760        STA (YLOC),Y ; Clear it
06D8 E6CC   1770        INC YLOC     ; Now is 1 higher
06DA 18     1780        CLC          ; Setup for forced branch
06DB 90CB   1790        BCC SIDE     ; Forced branch again
            1800 ; ******************************
            1810 ; Now side-to-side - left first
            1820 ; ******************************
06DD C6CE   1830 LEFT   DEC XLOC     ; To move it left
06DF A5CE   1840        LDA XLOC     ; Get it
06E1 8D00D0 1850        STA HPOSP0   ; Move it
06E4 60     1860        RTS          ; Back to MAIN - we're done
            1870 ; ******************************
            1880 ; Now right movement
            1890 ; ******************************
06E5 E6CE   1900 RIGHT  INC XLOC     ; To move it right
06E7 A5CE   1910        LDA XLOC     ; Get it
06E9 8D00D0 1920        STA HPOSP0   ; Move it
06EC 60     1930        RTS          ; Back to MAIN - we're done
            1940 ; ******************************
            1950 ; DATA statements
            1960 ; ******************************
06ED 53     1970 NAME   .BYTE "S:",$9B
06EE 3A
06EF 9B
06F0 FF     1980 PLAYER .BYTE 255,129,129,129,129,129,129,255
06F1 81
06F2 81
06F3 81
06F4 81
06F5 81
06F6 81
06F7 FF

    This program uses many of the routines we have already discussed – an erasing routine to clear out the player-missile area of memory, reading the joystick and moving the player, and the GRAPHICS 0 command. Here, we simply put them all together into one large program which performs all of the tasks necessary to implement a simple example of player-missile graphics in assembly language.

    Since it is analogous to the BASIC program we have already written, the assembly version begins by lowering RAMTOP by 8 pages to make room for player-missile memory. Lines 400 to 440 perform this function; line 410 stores the old value of RAMTOP for the erasing routine later. Line 450 tells the ATARI the location of PMBASE, the new value of RAMTOP. We'll use XLOC and XLOC+1 as a temporary indirect address location on page zero, to help in the erase routine. Lines 520 to 710 then reset a GRAPHICS 0 screen below the new location of RAMTOP. Lines 750 to 780 simply store the initial values of X and Y, the screen coordinates where we want the player to appear. These values will be used later, and these lines are here only to keep the analogy with the BASIC program. There is no need to store these values; we could just as easily have used the numbers directly later in the program. However, either way works, and it is slightly easier to change the program later if it is written in this manner. Lines 790 and 800 set up double-line resolution, and then we erase the entire player-missile area in lines 840 to 940.

    In the BASIC program, the place in memory where we insert the player to achieve the correct Y positioning on the screen is:

PMBASE+512+INITY

We know that 512 bytes above PMBASE is 2 pages, since each page contains 256 bytes. Therefore, we know that the high byte of this address, in our assembly language program, must be 2 higher than PMBASE. In lines 1000 to 1030, we get PMBASE, add 2, and store the result in YLOC+1, the high byte of the Y position in memory. The low byte is simply INITY, the initial Y position. Remember, the farther down the screen you want the player to appear, the higher in memory the player must be stored.

    To insert the player into the correct place in memory, we read one byte at a time from the table of data called PLAYER, and store it using indirect addressing into the memory location we just set up. When Y, our counter, equals 8, we're done, since we started at zero and have only 8 bytes to transfer. If our player had been larger, we simply would have changed the single byte in line 1110 to 1 higher than the number of bytes in the player. The initial X position of the player is read from INITX and stored in the horizontal position register for player zero, 53248. It is also stored in XLOC for use by the move-player routine.

    We then make the player red by storing the number 68 into the color register for player zero, 704, and enable player-missile graphics by storing a 3 into 53277, GRACTL.

    The main loop of this program is simplicity itself. We JSR to the routine which reads the joystick and moves the player, and then we enter a short delay loop. If we leave out this loop, the player moves so quickly that we can't control it at all! Next, we simply loop back to read the joystick and move the player again.

    Obviously, if you want to add some interest to this program, you can insert your own program logic into this main loop, to detect player-playfield collisions, or create obstacles, or anything else you may want in your game. If you are going to lengthen the program, however, you should change the origin to somewhere higher in memory. As it is, this program already occupies virtually all of page 6, so if you make it any larger without changing the origin, it will begin to overwrite DOS and you'll not be able to save or load it. Just change the origin to $6000 or some other safe high memory location. To test the program after assembling it, just type BUG to enter the debugger, and then type G600 for the original version (or G6000 if you change the origin).

    Now that you've seen how to implement player-missile graphics from assembly language, you should be able to write your own programs utilizing these same techniques. By doing this, as we've already seen from the need to insert a delay loop in the above program, you'll speed things up enormously and create smooth motion of players to greatly enhance your games. Have fun!


CREATING SOUND ON ATARI COMPUTERS

    We will begin our discussion of sound by learning how the ATARI produces sounds, and then we'll write an assembly language subroutine to mimic the BASIC SOUND command.

    Let's first look at the equates used for sound generation. The POKEY chip is responsible for the creation of all sounds in the ATARI computers, and it resides in memory from $D200 to $D2FF The sounds which add so much to enjoyment of games, and can even add to the ease of use of business programs if used properly, are divided into four voices. Each voice is controlled by two registers, located in pairs from $D200 to $D207. The first of each pair is the frequency control, and the second of each pair controls both the volume and distortion of the sound produced by that channel. These are:

100 AUDF1 = $D200    ; Audio channel 1 frequency
110 AUDC1 = $D201    ; Audio channel 1 control
120 AUDF2 = $D202    ; Audio channel 2 frequency
130 AUDC2 = $D203    ; Audio channel 2 control
140 AUDF3 = $D204    ; Audio channel 3 frequency
150 AUDC3 = $D205    ; Audio channel 3 control
160 AUDF4 = $D206    ; Audio channel 4 frequency
170 AUDC4 = $D207    ; Audio channel 4 control

    The respective frequency registers control the pitch of the sound or note being played. These registers actually divide the sound frequency by the number stored here. That is, if we store a 12 here, then the frequency produced is one-twelfth of the input frequency.

    The input frequency is controlled by the initialization of POKEY, by setting AUDCTL. The following chart describes the use of each bit in AUDCTL:


Bit Use                                                                         
0    Set to switch main clock from 64 kHz to 15 kHz
1    Set to insert high-pass filter into channel 2
2    Set to insert high-pass filter into channel 1
3    Set to join channels 4 and 3 for 16-bit resolution
4    Set to join channels 2 and 1 for 16-bit resolution
5    Set to clock channel 3 with 1.79 MHz
6    Set to clock channel 1 with 1.79 MHz
7    Set to convert the 17 bit poly-counter into 9 bits

    What does all this mean? Let's take it one bit at a time. Suppose you store a 10 into the frequency register of voice 1. We already know that this will cause one pulse to come out of that voice for each ten going in. Bit 0 of AUDCTL can switch the frequency of the incoming pulses between 64 kHz and 15 kHz. Kilohertz (kHz) stands for thousands of cycles, or pulses, per second. Obviously, if AUDCTL is set with bit 0 equal to zero, then the output frequency of voice 1 is 6.4 kHz. However, if we store a one into AUDCTL bit 0, then the output frequency of voice 1 will be 1.5 kHz, and a markedly lower tone will result. Bits 5 and 6 work exactly the same way, but if we set these to 1, the voices controlled by them, channels 3 and 1 respectively, produce much higher pitches, since they would be clocked at 1.79 MHz (millions of cycles per second), many times faster than either of the above frequencies.

    The two bits 1 and 2 of AUDCTL insert high-pass filters into channels 2 and 1, respectively. These high-pass filters are clocked by channels 4 and 3, respectively. That is, only sounds with a higher frequency than those currently playing in channel 3 will be heard in channel 1, and only sounds with a higher frequency than those currently sounding in channel 4 will be heard in channel 2. Some spectacular special effects are possible using these high-pass filters, and you'll certainly want to experiment to see what can be done.

    Since the frequency is stored in a single byte, the ATARI voices are limited to about a five octave range. However, using AUDCTL, it is possible to pair together sets of two voices using bits 3 or 4. This allows the two frequency registers for these voices to form a 16-bit number, giving a nine octave range. This decreases the number of voices available, but it would be perfectly feasible to produce one nine octave voice and two five octave voices, or even two nine octave voices. When two frequency registers are combined into one, the higher of the two frequency registers controls the high byte of the 2-byte number and the lower of the two controls the lower byte.

    The high bit of AUDCTL controls the polynomial counter. This is perhaps the most difficult concept of sound generation on the ATARI computers to grasp. Basically, the poly-counters produce a random sequence of pulses which repeats after some time. The higher the number of bits in the poly-counter, the longer the random sequence will be before the pattern repeats. There are three different poly-counters in the ATARI, and they all function as follows.

    Suppose you want some noise to be produced. Music is regular in tone, but noise is irregular and is harder to produce. The ATARI generates noise by producing a random sequence of pulses from the poly-counter and effectively ANDing these pulses together with the output of the frequency registers discussed above. Only when both pulses are ON is a sound produced. For example, if the frequency register says a pulse, or sound, should be produced, but the random pattern from the poly-counter is off at that time, no sound is produced. Therefore, although the output of the frequency register's divide-by-n system is a pure frequency, ANDing these pulses with the random sequence generated by the poly-counters produces noise. It should be apparent that different poly-counters produce different noise sounds, and the poly-counter used can be selected by bit 7 of AUDCTL, which is a 9-bit or 17-bit poly-counter.

    Furthermore, the distortion, or noise type, of the sound produced can also be changed by the distortion setting, much as in BASIC. The distortion is controlled by the upper 3 bits of the control register for each voice, AUDC1-4. These bits essentially choose how the sound is to be treated and which of the three polycounters is to be used for the distortion, as follows:

 Bits
7 6 5     Meaning                                                                 

0 0 0    Select using 5-bit, then 17-bit poly, divide by 2
0 E 1    Select using 5-bit poly, divide by 2
0 1 0    Select using 5-bit, then 4-bit poly, divide by 2
1 0 0    Select using 17-bit poly, divide by 2
1 E 1    No poly-counters used, just divide by 2
1 1 0    Select using 4-bit poly, divide by 2

E in the above table means that bit can be either a 1 or a 0. First, of course, the clock rate is divided by the frequency. Let's look at an example of how the distortion system works. We'll assume that the clock is running at 15 kHz, that we've stored 30 into the appropriate frequency register, and that the 3 high bits of the control register for that voice are 010. First, the clock rate is divided by the frequency register, in this case, 15000/30 = 500 Hz. Next, since the distortion bits are 010, the pulses at 500 Hz output from this operation are effectively ANDed with the output of the 5-bit polynomial counter. The output of this operation is then effectively ANDed with the output of the 4-bit poly-counter, and the frequency of the pulses successfully getting through this entire operation is divided by 2 to produce the final distorted sound.

    It should be obvious that with so many options to choose from, the ATARI is capable of many, many, many different sound effects. Some experimentation is clearly in order here. You may hear some really far-out sounds being produced!


    A SOUND SUBROUTINE

    Creation of sounds on the ATARI computers is extremely easy in BASIC, since the SOUND command allows us to turn any of the four available voices on or off at any desired distortion, pitch, volume, and frequency. Exactly the same functions are available from assembly language. We can write a subroutine to mimic the effects of the BASIC SOUND command, much as we did for the graphics commands in the previous section.

Listing 10.3

            0100 ; ******************************
            0110 ; SOUND equates
            0120 ; ******************************
D200        0130 AUDF1  =    $D200    ; Audio 1 frequency
D201        0140 AUDC1  =    $D201    ; Audio 1 control
D208        0150 AUDCTL =    $D208    ; Audio control
D20F        0160 SKCTL  =    $D20F    ; Serial port control
0101        0170 STORE2 =    $101     ; Temporary store
0000        0180        *=   $600
            0190 ; ******************************
            0200 ; The SOUND command
            0210 ; ******************************
            0220 ; Prior to calling this routine,
            0230 ; the X register should contain the
            0240 ; voice desired, the accumulator
            0250 ; should contain the distortion,
            0260 ; the Y register should contain
            0270 ; the volume desired, and STORE2
            0280 ; should contain the desired
            0290 ; frequency.
            0300 SOUND
0600 48     0310        PHA           ; Store distortion
0601 8A     0320        TXA           ; Double voice value
0602 0A     0330        ASL A         ; for offset to
0603 AA     0340        TAX           ; voice control
0604 AD0101 0350        LDA STORE2    ; Frequency into
0607 9D00D2 0360        STA AUDF1,X   ; right channel
060A 8C0101 0370        STY STORE2    ; For use later
060D A900   0380        LDA #0        ; To initialize the
060F 8D08D2 0390        STA AUDCTL    ; POKEY chip
0612 A903   0400        LDA #3        ; set these as
0614 8D0FD2 0410        STA SKCTL     ; indicated
0617 68     0420        PLA           ; Retrieve distortion
0618 0A     0430        ASL A         ; Now multiply by
0619 0A     0440        ASL A         ; 16 to get the
061A 0A     0450        ASL A         ; distortion into
061B 0A     0460        ASL A         ; the high nibble
061C 18     0470        CLC           ; Setup for add
061D 6D0101 0480        ADC STORE2    ; Add the volume
0620 9D01D2 0490        STA AUDC1,X   ; into right voice
0623 60     0500        RTS           ; That's all

    In this program, we double the value originally stored in the X register, which will select the particular voice to be used. This is because the sound registers are arranged in pairs, so each of the control registers and each of the frequency registers are two bytes apart in memory. The frequency is then retrieved from STORE2, which is used because we need four pieces of information for sound, and between the X and Y registers and the accumulator, we have only three storage locations at hand. The frequency is then stored in the appropriate frequency register in line 360, and the volume is temporarily stored until we need it. We then initialize POKEY in lines 380 to 410 and convert the distortion to the upper bits of the accumulator, adding the volume to obtain the number which needs to be stored in the appropriate audio control register to produce the sound desired. That's all there is to it!

    However, sound generation in assembly language suffers from the same problem it has in BASIC: no duration can be specified for the sound produced. Either the SOUND command in BASIC, or our equivalent routine in assembly language, simply starts the sound. We must turn the sound off after some predetermined time has elapsed, to create the note or sound effect we want. To turn off the sound, just store a zero into the appropriate control register, AUDC1-4. To measure the time elapsed, use either the timers already discussed, at locations 18 to 20 decimal, or use a routine like the one we used in the player-missile example for short delays:

     LDX #50
     LDY #0
LOOP DEY
     BNE LOOP
     DEX
     BNE LOOP

The time delay in this kind of routine is controlled by the initial value loaded into the X register; the larger the number, the longer the delay. These nested loops would take forever to execute if we programmed the counterpart to this routine in BASIC, but after assembly the delay is very short. Try it to see how fast 256 X 50, or 12,800 loops, can be.


    COUNTDOWN TIMERS

    The third way of keeping track of time on the ATARI computers involves the use of countdown timers. AUDF1, AUDF2, and AUDF4 can act as countdown timers in the following way. When any nonzero value is stored into STIMER ($D209), the values stored in AUDF1, AUDF2, and AUDF4 begin to decrease. Each of these three registers has an appropriate vector location, as outlined below:

Timer      Vector location          
AUDF1   $210, $211 (VTIMR1)
AUDF2   $212, $213 (VTIMR2)
AUDF4   $214, $215 (VTIMR4)

If we place the address of a short routine to turn off sound into one of these vector locations, an interrupt will be generated when the appropriate timer has counted down to zero, and control will shift to your routine to turn off the sound. Just remember to end this routine with an RTI rather than an RTS. Before using this type of timer, you must place the appropriate value into the interrupt request enable byte, IRQEN ($D20E). To enable VTIMR1, set bit 0 of IRQEN; to enable VTIMR2, set bit 1 of IRQEN; and to enable VTIMR4, set bit 2 of IRQEN.

    You should now be able to create any sounds available from BASIC using assembly language, and here's one which can't be produced from BASIC because of the speed required. If bit 4 of any of the audio control registers is set to 1, a short "pop" can be heard. This is caused by pushing the speaker cone out once, compressing the air in front of the speaker, which you hear as a "pop:' If we set and reset this bit quickly, we can produce a sound just by compressing air in front of the speaker. Try this:

LOOP LDA #16
     STA  AUDC1
     (insert delay for frequency)
     LDA #31
     STA AUDC1
     (insert delay for frequency)
     JMP LOOP

The speaker on your TV or monitor will vibrate back and forth, producing sound in a totally different way from that described above. In this example, we are simply moving the speaker directly in order to produce sound.


Return to Table of Contents | Previous Chapter | Next Chapter