Volume 3 -- Issue 9 | June 1983

In This Issue...

- Spiral Screen Clear
- Breaking and Entering
- Binary to Decimal Conversion
- The Tiniest Motherboard
- Replacing INIT Can Be Dangerous
- Reformatting a Lot of Text
- Track Balls
- Ampersand Monitor Caller

A New Look?

The cover and a few of the inside pages of this issue look a little different, don't they? [in the printed edition, the mentioned pages were printed with a dot matrix printer instead of the normal NEC Spinwriter.] Well, our offices were burglarized last night, and the Spinwriter we use for newsletter printing was damaged. The thieves also made off with three complete computer systems and a load of software. They even got the Track Ball which I used for the article in this issue! See page 9 for more details.

65C02 Note

Don Lancaster just called to report that he has gotten his hands on samples of the GTE G65SC02 processor. It does drop right into his Apple //e, and runs just fine! Note that this is the GTE version, which does not have the instructions to set, reset, and test single bits. Those are in the Rockwell chip, which still hasn't shown up. We'll keep passing on whatever we hear.

Spiral Screen Clear | Roger Keating modified and documented by Bob Sander-Cederlof

[ Roger is the author of numerous excellent war games for the Apple, some published by Strategic Simulations. Titles include Southern Command, Operation Apocalypse, Germany 1985, and Rapid Deployment Force. He is also director for Australasia of the International Apple Core. A previous version of this program was published in the newsletter of one of the Australian Apple user groups. ]

The following program demonstrates an innovative method for clearing the Apple text or lo-res graphics screen. Rather than just switching to instant blackness, it makes the process visually interesting.

The entire screen is viewed as one long coiled line. For 55 seconds this line unwinds on the screen, with blanks being fed into the center and visible characters shifting out at the bottom left corner. Here is a simplified diagram, on a 10-column 6-line screen:

          0   1   2   3   4   5   6   7   8   9
         -----------------------------------------
         | V | < | < | < | < | < | < | < | < | < | 0
         -- ----------------------------------- --
         | V | V | < | < | < | < | < | < | < | ^ | 1
         -- --- --------------------------- --- --
         | V | V | V | < | < | < | < | < | ^ | ^ | 2
         -- --- --- ------------------- --- --- --
         | V | V | V | > | > | > | > | ^ | ^ | ^ | 3
         -- --- --- ----------------------- --- --
         | V | V | > | > | > | > | > | > | ^ | ^ | 4
         -- --- ------------------------------- --
         | X | > | > | > | > | > | > | > | > | ^ | 5
         -- --------------------------------------
           0   1   2   3   4   5   6   7   8   9

There are a total of 60 characters. By calling 60 times a routine which rotates the spiral, and inserting a blank at line 3 column 3 after each rotation, I can blank out the entire 60 characters. The <, >, V, and ^ symbols show the direction each character cell will move during the rotation. The "X" in line 5 column 0 is shifted out to never-never land. (In the old days, we said it went into the "bit bucket".)

Well, that is the general idea. But I did it for the whole 24x40 screen, a little too much to show in this small space.

Here is a description of the highest level of the spiral clear program, in a language a little like BASIC:

         FOR VCNT = 1 TO 24
             FOR HCNT = 1 TO 40
                 ROTATE SCREEN
                 STORE BLANK AT TRAIL'S END
             NEXT HCNT
         NEXT VCNT

The use of two loops is not necessary, because the loop variables are not used at all inside the loops. We could just as well write it like this:

         FOR CNT = 1 TO 960
             ROTATE SCREEN
             STORE BLANK
         NEXT CNT

However, thinking ahead to the assembly language implementation, we know that counters are easier to manage if they have values less than 256. Therefore I like the first version better. Furthermore, counters in assembly language are frequently easier to work with if they count backwards. I ended up with the code in lines 1130-1270 of the program.

The value "$628+12" in line 1210 happens to be the memory address of the inner end of the spiral. I figured it out on paper, tried it, corrected my figuring, and tried it again.

You can add some cute wrinkles here and there. Like putting a "pause if key pressed" routine right after calls to ROTATE.SCREEN. Like storing something besides a blank. Like feeding the character from the bottom left corner into line 12 column 12, so that the original text is restored. Like varying the character stored during the 960 rotations. Like using a color value in lo-res graphics mode. I tried a lot of these, and it is amazing how much you can learn this way.

The rotation process involves four separate steps: sliding a column on the left side down, slipping an upper row to the left, shoving a column on the right side up, and scooting a lower row to the right. Appealing once again to a higher level language, it might look like this:

         XL=0 : XR=39
         YT=0 : YB=23
         WHILE YT<YB
             SLIDE COLUMN XL DOWN
             SLIP ROW YT LEFT
             SHOVE COLUMN XR UP
             SCOOT ROW YB RIGHT
             ADJUST XL, XR, YT, YB
         LOOP

It turns out it is not quite that simple, but almost. The assembly language is in lines 1300-1530.

The initial call to DOWN at line 1390 moves the leftmost column of characters down, ignoring the bottom left cell. If that cell were not ignored, it would slide to some undetermined place in memory, not necessarily even on the screen. Where it goes depends on what method is used to compute screen addresses from line number. Anyway, it would try to go to a 25th line, which does not exist.

It turns out that if BASCALC in the monitor ROM is used, the character is moved into $478, which is not part of the screen. It is usually a safe location, but some peripheral boards or DOS might use it. If you don't care about saving $478, or if you use a different method which leads to a completely safe address for the 25th line, you could omit lines 1390-1400.

I still need to show you the routines DOWN, LEFT, UP, and RIGHT. Here they are in pseudo-code:

         DOWN:  FOR Y = YB-1 TO YT STEP -1
                    S(XL,Y+1) = S(XL,Y)
                NEXT Y

         LEFT:  FOR X = XL+1 TO XR
                    S(X-1,YT) = S(X,YT)
                NEXT X

         UP:    FOR Y = YT+1 TO YB
                    S(XR,Y-1) = S(XR,Y)
                NEXT Y

         RIGHT: FOR X = XR-1 TO XL STEP -1
                    S(X+1,YB) = S(X,YB)
                NEXT X

The assembly language is in lines 1550-2240.

Each of these routines needs a function to compute the base address of a line on the screen. I called the subroutine MAKE.BASE, and implemented it in two different ways. The easiest way is to call on the subroutine BASCALC at $FBC1 in the monitor ROM.

BASCALC accepts the line number (0-23) in the A-register, computes the base address by a series of shift and masking operations, and puts the beginning address of the line into $28 and $29. No other registers are used, so it is a nice subroutine to have available. (Its only problem is that it takes a full 40 microseconds to do the job.) Since I call MAKE.BASE with the line number in the X-register, it can be written like lines 2310-2320.

The faster way to get a base address loaded is to use a table of addresses. I put the high byte of each base address in a 24-byte table, and the low byte in another. The line number in the X-register indexes this table. MAKE.BASE is in lnes 2340-2380, and the table in lines 2400-2450.

The table lookup is about twice as fast as BASCALC's 40 microseconds. Since MAKE.BASE is called once each for LEFT and RIGHT, and twice for each character moved for UP and DOWN, and the whole set is called 960 times, the overall effect is large. Using BASCALC the total time to clear the screen is 55 seconds; with the table lookup it is 40 seconds. On the other hand, the table and the code to read it take up 59 bytes, while the call to BASCALC takes only 4 bytes.

      1000 *--------------------------------
      1010 *      SPIRAL CLEAR BY ROGER KEATING
      1020 *--------------------------------
      1030 HCNT   .EQ $00
      1040 VCNT   .EQ $01
      1050 XL     .EQ $02
      1060 XR     .EQ $03
      1070 YT     .EQ $04
      1080 YB     .EQ $05
      1090 BASE   .EQ $28
      1100 *--------------------------------
      1110        .OR $800
      1120 *--------------------------------
      1130 SPIRAL.CLEAR
      1140        LDA #24      FOR 24 LINES
      1150        STA VCNT
      1160 .1     LDA #40      FOR 40 COLUMNS
      1170        STA HCNT
      1180
      1190 .2     JSR ROTATE.SCREEN
      1200        LDA #$A0     STORE BLANK IN
      1210        STA $628+12  MIDDLE OF SCREEN
      1220
      1230        DEC HCNT     NEXT HCNT
      1240        BNE .2
      1250        DEC VCNT     NEXT VCNT
      1260        BNE .1
      1270        RTS          FINISHED!
      1280
      1290 *--------------------------------
      1300 ROTATE.SCREEN
      1310        LDA #0
      1320        STA XL       LEFT END
      1330        STA YT       TOP
      1340        LDA #23
      1350        STA YB       BOTTOM
      1360        LDA #39
      1370        STA XR       RIGHT END
      1380
      1390        JSR DOWN     START LEFT SIDE
      1400        BEQ .2       ...ALWAYS
      1410
      1420 .1     JSR DOWN     SLIDE LEFT SIDE DOWN
      1430        DEC YB       MOVE BOTTOM UP
      1440 .2     JSR LEFT     SLIP TOP LINE LEFT
      1450        INC XL       MOVE LEFT EDGE IN
      1460        JSR UP       SHOVE RIGHT SIDE UP
      1470        INC YT       MOVE TOP DOWN
      1480        JSR RIGHT    SCOOT BOTTOM LINE RIGHT
      1490        DEC XR       MOVE RIGHT EDGE IN
      1500        LDA YT
      1510        CMP YB
      1520        BCC .1       IF YT<YB, BRANCH
      1530        RTS          FINISHED
      1540
      1550 *--------------------------------
      1560 *      MOVE LEFT SIDE DOWN
      1570 *      FOR Y=YB-1 TO YT STEP -1
      1580 *      S(XL,Y+1)=S(XL,Y) : NEXT
      1590 *--------------------------------
      1600 DOWN   LDY XL       COLUMN BEING MOVED DOWN
      1610        LDX YB       BOTTOM CELL IN COLUMN
      1620 .1     DEX
      1630        JSR MAKE.BASE
      1640        LDA (BASE),Y
      1650        PHA          SAVE CHAR IN CELL
      1660        INX
      1670        JSR MAKE.BASE
      1680        PLA
      1690        STA (BASE),Y
      1700        DEX
      1710        CPX YT       AT TOP OF COLUMN YET?
      1720        BNE .1       NO
      1730        RTS          YES
      1740 *--------------------------------
      1750 *      MOVE RIGHT SIDE UP
      1760 *      FOR Y=YT+1 TO YB
      1770 *      S(XR,Y-1)=S(XR,Y) : NEXT
      1780 *--------------------------------
      1790 UP     LDY XR       COLUMN BEING MOVED UP
      1800        LDX YT       TOP CELL IN COLUMN
      1810 .1     INX
      1820        JSR MAKE.BASE
      1830        LDA (BASE),Y
      1840        PHA          SAVE CHAR IN CELL
      1850        DEX          BACK UP
      1860        JSR MAKE.BASE
      1870        PLA
      1880        STA (BASE),Y
      1890        INX
      1900        CPX YB       AT BOTTOM OF COLUMN YET?
      1910        BNE .1       NO
      1920        RTS          YES
      1930 *--------------------------------
      1940 *      MOVE TOP LINE LEFT
      1950 *      FOR X=XL+1 TO XR
      1960 *      S(X-1,YT)=S(X,YT) : NEXT
      1970 *--------------------------------
      1980 LEFT   LDX YT       TOP LINE
      1990        JSR MAKE.BASE
      2000        LDY XL
      2010 .1     INY
      2020        LDA (BASE),Y
      2030        DEY
      2040        STA (BASE),Y
      2050        INY
      2060        CPY XR       LAST COLUMN YET?
      2070        BNE .1       NO
      2080        RTS
      2090 *--------------------------------
      2100 *      MOVE BOTTOM LINE RIGHT
      2110 *      FOR X=XR-1 TO XL STEP -1
      2120 *      S(X+1,YB)=S(X,YB) : NEXT
      2130 *--------------------------------
      2140 RIGHT  LDX YB       BOTTOM LINE
      2150        JSR MAKE.BASE
      2160        LDY XR
      2170 .1     DEY
      2180        LDA (BASE),Y
      2190        INY
      2200        STA (BASE),Y
      2210        DEY
      2220        CPY XL       FIRST COLUMN YET?
      2230        BNE .1       NO
      2240        RTS
      2250
      2260 *--------------------------------
      2270 *      POINT BASE TO SCREEN LINE
      2280 *      (X) = LINE #
      2290 *--------------------------------
      2300 MAKE.BASE
      2310 *      TXA          ALTERNATE APPROACH
      2320 *      JMP $FBC1    USING BASCALC IN ROM
      2330
      2340        LDA HI,X     FAST APROACH USING
      2350        STA BASE+1   TABLE LOOKUP
      2360        LDA LO,X
      2370        STA BASE
      2380        RTS
      2390 *--------------------------------
      2400 HI     .HS 0404050506060707
      2410        .HS 0404050506060707
      2420        .HS 0404050506060707
      2430 LO     .HS 0080008000800080
      2440        .HS 28A828A828A828A8
      2450        .HS 50D050D050D050D0

Breaking and Entering | Bill Morgan

We had a burglary here last night (today is May 26). Thieves got into the building somehow, and broke through the doors of several businesses, including your favorite software house and newsletter publisher.

They got three complete computer systems, including a two day old Apple //e that belonged to Judy Preston (she handles your orders), and an Apple /// system on loan from Apple Computer!

Just in case somebody tries to sell any of you some used Apple equipment, here's the list of what we lost, including serial numbers where known:

         Apple ][ Plus Computer         1498251
         STB 16K RAM Card
         Epson Printer Card
         Apple High-Speed Serial Card
         Wico Track Ball w/Interface
         Apple Disk Controller
         2 Apple Disk Drives            414611 &
         Epson MX-80 Printer
         NEC Green Monitor              1315572
         R-H Super Fan II

         Apple //e Computer             152413
         Extended 80-Column Card
         Apple Disk Controller
         Apple Disk Drive
         Leedex B/W Monitor

         Apple /// Computer
         Apple /// Monitor
         Apple Silentype Printer

         TI Programmer Calculator (LCD Display)

If you do see any of the above items, call your local police and/or S-C Software. Try to find out the name and address of the person selling the goods.

The thieves also took who-knows-how-many disks (at least three Flip-Files, two library cases, and many loose ones), containing the text and code from about the last three newsletters, all the disks from the Apple /// project (including the source code for the new assembler), several projects-in-progress, and whatever was handy. It will probably be months until we know what all is gone.

As I mentioned on the front page, the Spinwriter was damaged. They apparently got about half way down the hall carrying the printer, and then dropped it onto the concrete floor! We don't yet know what will be necessary to repair it.

Anyway, we're all alive and well, and we're going to carry on. See you next month!

Binary to Decimal Conversion | Bob Sander-Cederlof

Dear Assembly Line,

I hope you can answer a question I have concerning assembly language. I am just a beginner.

How can I convert hex numbers to decimal from within a running machine language program? That is, take bytes from locations A and A+1, convert the bytes to their decimal equivalent, and store the resulting ASCII bytes in other memory locations.

As a new member of A.P.P.L.E., I called their technical assistance line. They advised me to call Don Williams, who in turn directed me to Val Golding, editor of Call APPLE magazine. Val referred me to old issues of Call APPLE and Apple Orchard which I don't have available.

This appears to be a sticky question that nobody wants to deal with. There are a million ways to do this from BASIC, but I have yet to see one that will do it strictly within machine language.

(signed) I. M. Perplexed Anaheim, California

------

Dear Mr. Perplexed,

Your odyssey in search of a conversion program sounds as frustrating as it probably was! Let me assure you no one is trying to avoid dealing with these kinds of programs.

It is just that there are hundreds of variations. And many of them have been printed during the past five or six years in Micro, Nibble, Call APPLE, Apple Orchard, Kilobaud, Byte, and hundreds of Apple user group newsletters. All or most back issues of the major magazines are available through anthology volumes such as The Nibble Express, The Best of Micro, and Peeking at Call APPLE.

Also, almost all of the books written to teach 6502 assembly language programming include as examples subroutines to convert from binary to decimal and decimal to binary. We especially recommend Roger Wagner's "Assembly Lines--The Book" and Lance Leventhal's "6502 Subroutines". In fact, we even sell both of them at a slight discount.

I don't want to send you away looking for yet another source of information, so here is a routine I have used.

The subroutine assumes that you have placed the binary value into a variable called BINARY.VALUE, and places the converted result into DECIMAL.VALUE as a series of five ASCII characters. After conversion, the value in BINARY.VALUE will be zeroed.

This particular version of conversion produces leading zeroes for values below 10000. Variations I have used substitute leading blanks, or left justify with trailing blanks, or return a left justified value with a digit count.

The general method involves subtracting 10000 as many times as possible, and counting the times to get the first digit; subtracting 1000 from the remainder as many times as possible and counting the times to get the next digit; and so on. To simplify indexing, the constants 1, 10, 100, 1000, and 10000 are stored with the low-order bytes in one table, and the high-order bytes in another.

Line 1060 sets the Y-register to zero, for use as a pointer to the byte positions in DECIMAL.VALUE. If you were storing into a line buffer, you might enter th routine with the Y-register pointing to the starting place in the line and skip this initialization step.

Line 1070 sets X=4, which is one less than the number of digits you want to convert. X=4 gives five digits; if you are sure the value of the decimal number will be smaller, you can set X for fewer digits and enter past this point.

The loop from line 1090 through 1290 develops each digit in turn. Line 1090 starts a new digit by loading an ASCII zero. This value is pushed onto the stack during the subtraction that follows. Each time a subtraction is successful, it will be pulled off, incremented, and then pushed back on.

Lines 1120-1170 subtract the current divisor without storing the difference back into BINARY.VALUE. If the difference is positive, it is stored and the digit incremented. If the result is negative, then that was one subtraction too many, so the difference is discarded and the digit is retrieved from the stack at line 1250.

Line 1260 stores the converted digit into DECIMAL.VALUE, and line 1270 advances the digit pointer. Line 1280 advances the divisor pointer. If there are more divisors, line 1290 branches back to convert the next lower digit.

I have used a form of this subroutine inside the S-C Macro Assembler. That version includes options to also print the decimal value as it is being converted. Different entry conditions control whether the number will be printed, stored in a buffer, or both. I also allow control over the leading zeroes/blanks option and the number of digits.

This is only one method out of many, but it is fairly compact and easy to understand, without being too slow.

      1000 *SAVE S.CONVERT BIN TO DEC
      1010 *---------------------------------
      1020 *      CONVERT VALUE TO DECIMAL CHARACTERS
      1030 *      BY BOB SANDER-CEDERLOF
      1040 *---------------------------------
      1050 CONVERT
      1060        LDY #0       POINT AT FIRST CHARACTER
      1070        LDX #4       5 DIGITS
      1080 *--------------------------------
      1090 .1     LDA #$B0     SET DIGIT TO ASCII ZERO
      1100 .2     PHA          PUSH DIGIT ON STACK
      1110        SEC          SUBTRACT CURRENT DIVISOR
      1120        LDA BINARY.VALUE
      1130        SBC PLNTBL,X
      1140        PHA          SAVE BYTE ON STACK
      1150        LDA BINARY.VALUE+1
      1160        SBC PLNTBH,X
      1170        BCC .3       LESS THAN DIVISOR
      1180        STA BINARY.VALUE+1
      1190        PLA          GET LOW BYTE OFF STACK
      1200        STA BINARY.VALUE
      1210        PLA          GET DIGIT FROM STACK
      1220        ADC #0       INCREMENT DIGIT (CARRY WAS SET)
      1230        BNE .2       ...ALWAYS
      1240 .3     PLA          DISCARD BYTE FROM STACK
      1250        PLA          GET DIGIT FROM STACK
      1260        STA DECIMAL.VALUE,Y
      1270        INY          POINT TO NEXT DIGIT
      1280 .4     DEX          POINT TO NEXT DIVISOR
      1290        BPL .1
      1300        RTS          RETURN
      1310 *---------------------------------
      1320 PLNTBL .DA #1
      1330        .DA #10
      1340        .DA #100
      1350        .DA #1000
      1360        .DA #10000
      1370 PLNTBH .DA /1
      1380        .DA /10
      1390        .DA /100
      1400        .DA /1000
      1410        .DA /10000
      1420 *--------------------------------
      1430 BINARY.VALUE  .BS 2
      1440 DECIMAL.VALUE .BS 5
      1450 *--------------------------------

The Tiniest Motherboard | Bob Sander-Cederlof

When I was at the Boston Applefest last week, Chad Pennebaker of Douglas Electronics showed me what he called the world's smallest Apple Mother Board. It is a bus board really, with ten Apple-Compatible 50-pin sockets labeled A, B, C, and 1 thru 7. The seven numbered slots accept almost any board made for Apples. Slot A is designed to hold a 6502 card, slot B a RAM card, and slot C a ROM card.

Some fantastic prices too: mother board, $95; CPU card, $90; 64K RAM card which maps with 48K from 0 to $BFFF, and 12K from $D000 to $FFFF, and another 4K from $D000 to $DFFF (sound familiar?), $190; 12K EPROM card (without EPROMS), $70.

Chad showed me a beautiful little cabinet it all fits in, Apple-beige metal with walnut sides. I didn't catch the price. There is also a power supply available, which looks just like the Apple unit. A card which provides keyboard and screen functions is on the way.

Here's how to reach them: Douglas Electronics, Inc., 718 Marina Blvd., San Leandro, CA 94577. Or call at (415) 483-8770.

Replacing INIT Can Be Dangerous | Bill Morgan

I have recently spent several days beating my head against an impossible bug, and I'll bet that some of you have run into, or will hit, the same thing. Here's my tale....

One of the most common enhancements to Apple's DOS is the addition of new commands. You do this by replacing one of the existing commands, usually INIT, VERIFY, or MAXFILES, with new code and a new name. I got into trouble replacing INIT, and had a great time figuring out why!

The SHOW Command ...

I was working with the SHOW command, as published in the July, 1982 Apple Assembly Line. Typing SHOW <filename> displays any sequential text file on the screen; it's a really handy command to have.

SHOW is installed in place of INIT. Making the change involves placing the new code over the File Manager's INIT handler at $AE8E and at $A54F, changing the command name at $A884, and altering the table of permissible and required keywords at $A909. These are the usual steps to replace a command. See the July '82 issue for more details about SHOW.

... and File Manager Calls

The S-C Word Processor uses one of the popular high-speed methods to handle text files. It calls the File Manager to OPEN the file, then uses its own code to LOAD or SAVE text, by directly calling RWTS. So far, so good, this is basically normal. However, after I installed the SHOW command in DOS, I couldn't SAVE new files from the Word Processor! SAVEing to an existing file worked just fine, but trying to create a new file returned a FILE NOT FOUND error. What???

A little bit of digging convinced me that this was impossible. What does changing INIT have to do with OPENing a text file? There is that table which tells what a command can or cannot do, but SHOW is nowhere near OPEN, and besides, the table is used by the command handler, not by the File Manager. You tell the File Manager that it can create a file, if necessary, by setting the X register to zero before doing the JSR $3D6. The Word Processor does that just right.

A lot more digging brought the solution to light. When the File Manager (FM) OPENs a file, it does refer to that table at $A909 to see if it can create a new file. It loads the X register with a command index kept at $AA5F, then checks the corresponding table entry. But what does that have to do with INIT? Read on...

When you enter FM through $3D6 it jumps to a special entry at $AAFD. The first thing it does there is check the X register to see if it will be allowed to create a new file. If X is nonzero (no new file), FM stores a 2 in the command index ($AA5F). That is the index for the LOAD command, which cannot create a file. If X is zero (new file allowed), FM stores that zero in the command index. And zero is the index to the INIT command, which does create a new file (usually HELLO).

So there it is. If we replace INIT with a command that is not allowed to create a new file, we will mess up programs that call the File Manager directly to OPEN new files. Ouch!!

Fixing the Problem

There are several possible ways to avoid such trouble:

1. Don't mess with INIT. If you leave it alone, it won't bite you. But, INIT is so useless in a running program and offers so much space for new code. It's sure hard to resist.
2. Only replace INIT with other commands that are allowed to create new files. That's OK, but limiting.
3. Add these additional patches to whatever new command you're playing with:
   1. Put JMP FM.ENTRY.PATCH at $AAFD.
   2. And put FM.ENTRY.PATCH wherever is convenient (in SHOW I put it at $AEA5, just after the PAUSE.CHECK code):

              FM.ENTRY.PATCH
                     CPX #0          Create new file?
                     BEQ .1          0 means yes
                     LDX #2          No, use LOAD index
                     BNE .2          ... Always
              .1     LDX #4          Yes, use SAVE index
              .2     JMP $AB03       Return to File Manager

Conclusion

This kind of problem is a great example of why you need to be very careful about patching an operating system: it's hard to tell what kind of "impossible" interactions will turn up. On the other hand, how else can we learn about what's really going on inside our Apples? And what else can replace the "AHA!!! Ahhh..." sensation you get when you unravel a really cute bug? Keep on patchin'.

Reformatting a Lot of Text | Bob Sander-Cederlof

At the Boston AppleFest I picked up a copy of David Durkee's SoftGraph program. You probably read the series of four articles in Softalk (Jan thru Apr, 1983) in which he developed this fine little system for editing data and creating pie, bar, and line charts. If you didn't, let me recommend them.

(If you don't get Softalk, why not? It's free! The best magazine there is for Apple owners! Send your serial number to Al Tommervik at Softalk, Box 60, North Hollywood, CA 91603 today!)

Anyway, back to SoftGraph. On the disk is a 107 sector binary file loaded with ASCII characters. A small program on the disk will display or print the text from this file. Never satisfied with things as they come, I wanted to load the file into my word processor. The problem itself may be irrelevant to you, but the steps to solving it can be quite instructive. Follow along now....

My word processor will read binary or text files, but it expects the data to be ASCII with the high bits all set to 1. Naturally, Durkee's file had all high bits set to 0. "That's OK, I'll just run the handy little code from AAL Dec 82, 'Add Bit-Control to Apple Monitor' "

So I did, but setting all the high bits wasn't quite enough. A second feature of the file came to light: no carriage returns anywhere. Each line was padded with trailing blanks to fill exactly 40 bytes. Even the blank lines contained 40 blanks. I fidgeted in my chair, and pulled a little hair.

After several false starts I finally resorted to a trick I learned back in the dark ages before structured programming, with its scientific rules and esoteric vocabulary, was invented. I constructed a flow chart! I do this every once and a while, as a thinking tool. But you probably won't find any in my documentation, because they are just a tool. I usually modify my thinking as I code, which obseletes the chart. Most charts are done on odd bits of scrap paper, and don't last overnight.

Here was my plan. First, BLOAD the file somewhere in memory and find its end by looking at $AA60 and $AA61. This pair of bytes contains the length of the last file loaded. Second, run a conversion program to change the data IN PLACE. Third, BSAVE the modified data on a new file. I decided to save time by manually typing the BLOAD and BSAVE commands, rather than writing code to do these steps inside my conversion program.

It so happened that the file would fit between $2000 and $8977. With the S-C Assembler in the language card at $D000, this space was available. The source code for my conversion program fit above $9000 running up to $95FF. The object code started at $800, and didn't make it out of that page.

Without drawing the flow chart for you, here is the general idea of the conversion process:

1. Set up two pointers, one for retrieving characters out of the text and the other for storing converted characters into the text.
2. Get 40 characters from the text into a little buffer. If at the end (marked by a 00 byte), quit.
3. Scan backwards in the little buffer to the first non-blank character.
4. If the whole line is blank, store a carriage return into the text.
5. Otherwise, copy the little buffer back into the text, with the high bit set on each byte. Then add one blank, because we need to maintain a blank between words. (They may not print on separate lines after re-formatting with my word processor.)
6. Back to 2.

(I'm sorry, but except for the lines and boxes, that does look a lot like a flow chart.)

One little wrinkle I thought about but didn't implement until later had to do with double spacing between paragraphs. I handled it by checking the length of the previous line after stuffing the carriage return for a blank line. If the previous line was non-blank, I sent an extra carriage return back into the text.

My routine counts on the assumed condition that the resulting text will be shorter than the original text. I knew the file began with several lines of 40 blanks, each being replaced with a single carriage return, so I felt confident that all would work. If I was wrong, I would be storing modified data on top of yet-unprocessed data, with wild results. Don't worry, it worked out OK.

Lines 1050-1090 define some variables. The data will begin at $2000, because I put it there with a "BLOAD DOCFILE,A$2000" command. GET.PNTR and PUT.PNTR will start out pointing at $2000. Each time I pull 40 characters out of the data I will add 40 to GET.PNTR. Each time I put one character back into the data I will add one to PUT.PNTR. LAST.LINE.SZ keeps track of previous line length so I can get double spacing between paragraphs.

BUFFER is for the forty characters pulled out of the data each cycle through the program. I frequently use $200 for buffers like this, because it is a nice handy area. And most Apple software uses $200 for a buffer. But...since the monitor does use $200, it can be difficult to see what is put there by my program. Hence, this time I put the buffer at $280 instead.

Lines 1110-1390 implement the logic flow described above. Previous line length starts out zero when there were no previous lines (1120-1130). The two pointers get initialized at 1140-1190.

Calling GET.40.CHARS pulls the next 40 bytes out of the data into my buffer at $280. If a 00 byte is hit, the subroutine returns with carry set; if not, carry is clear. Line 1210 acts on the carry info to end the program if we are done.

TRUNCATE.BLANKS starts at the end of the buffer looking for non-blanks. If the whole buffer is blank, the subroutine sets carry. If not, it clears carry. Line 1230 acts on carry. Non-blank lines are copied back into the data by PUT.CHARS, and then PUT.CHAR is used to add one trailing blank. Blank lines cause a return ($8D) to be put into the data, and if the previous line was non-blank a second return is added.

PUT.CHAR (lines 1410-1470) stores the byte in the A-register into the data where PUT.PNTR points. Then PUT.PNTR is incremented. PUT.CHARS (lines 1810-1880) calls PUT.CHAR once for each character in the buffer, omitting all the trailing blanks.

With trepidation I typed $800G to execute it, after being sure I had saved the source code and then opened both disk drive doors. To my surprise, the program ran without failure the very first time! Not to say it was perfect.

After execution I looked at PUT.PNTR to see where the new data ended. It was $70C7, if I remember correctly. Then I BSAVE'd with "BSAVE DOCFILE 2,A$2000,L$50C7", and loaded my word processor.

In the word processor I loaded the new DOCFILE 2, and it was all there. Somehow two small sections were missing all the carriage returns, but all the rest was perfect. I still don't know what caused those two sections (total of about ten lines) to fail, but it isn't all that important. I used the word processor to fix them, and the job was done.

      1000 *SAVE S.CONVERT DURKEE
      1010 *--------------------------------
      1020 *      CONVERT DURKEE'S DOCFILE
      1030 *      TO S-C WORD ASCII FORMAT
      1040 *--------------------------------
      1050 DATA         .EQ $2000
      1060 GET.PNTR     .EQ $00,01
      1070 PUT.PNTR     .EQ $02,03
      1080 LAST.LINE.SZ .EQ $04
      1090 BUFFER       .EQ $280
      1100 *--------------------------------
      1110 CONVERT
      1120        LDA #0
      1130        STA LAST.LINE.SZ
      1140        LDA #DATA
      1150        STA GET.PNTR
      1160        STA PUT.PNTR
      1170        LDA /DATA
      1180        STA GET.PNTR+1
      1190        STA PUT.PNTR+1
      1200 .1     JSR GET.40.CHARS
      1210        BCS .3
      1220        JSR TRUNCATE.BLANKS
      1230        BCS .2       EMPTY LINE
      1240        JSR PUT.CHARS
      1250        LDA #$A0     BLANK
      1260        JSR PUT.CHAR
      1270        BNE .1       ...ALWAYS
      1280 .2     LDA #$8D     <RETURN>
      1290        JSR PUT.CHAR
      1300        LDA LAST.LINE.SZ
      1310        BEQ .1
      1320        LDA #0
      1330        STA LAST.LINE.SZ
      1340        LDA #$8D
      1350        JSR PUT.CHAR
      1360        BNE .1       ...ALWAYS
      1370 .3     LDA #0       <EOL>
      1380        JSR PUT.CHAR
      1390        RTS
      1400 *--------------------------------
      1410 PUT.CHAR
      1420        LDY #0
      1430        STA (PUT.PNTR),Y
      1440        INC PUT.PNTR
      1450        BNE .1
      1460        INC PUT.PNTR+1
      1470 .1     RTS
      1480 *--------------------------------
      1490 GET.40.CHARS
      1500        LDY #0
      1510 .1     LDA (GET.PNTR),Y
      1520        CMP #0
      1530        BEQ .3       END OF DATA
      1540        ORA #$80
      1550        STA BUFFER,Y
      1560        INY
      1570        CPY #40
      1580        BCC .1
      1590        LDA #39      CARRY SET
      1600        ADC GET.PNTR
      1610        STA GET.PNTR
      1620        BCC .2
      1630        INC GET.PNTR+1
      1640 .2     CLC
      1650        RTS
      1660 .3     SEC          END OF DATA
      1670        RTS
      1680 *--------------------------------
      1690 TRUNCATE.BLANKS
      1700 .1     LDA BUFFER-1,Y
      1710        CMP #$A0     BLANK?
      1720        BNE .2       NO
      1730        DEY          YES
      1740        BNE .1
      1750        SEC          EMPTY LINE
      1760        RTS
      1770 .2     STY LAST.LINE.SZ
      1780        CLC
      1790        RTS
      1800 *--------------------------------
      1810 PUT.CHARS
      1820        LDX #0
      1830 .1     LDA BUFFER,X
      1840        JSR PUT.CHAR
      1850        INX
      1860        CPX LAST.LINE.SZ
      1870        BCC .1
      1880        RTS

Track Balls | Bill Morgan

If you have ever played Centipede or Missile Command in the arcade, then you know that a track ball is about the best control device made. A joystick usually can move across the whole screen a little faster, but a track ball gives much finer, smoother control. A "mouse" is said to be even better, but the only mouse I have seen advertised for the Apple ][ sells for about $300-400. Besides, I never have 2-3 square feet of free space on my desk, for the mouse to run around on.

Several track balls for the Apple have appeared recently, all in the $60-80 price range. I have tried out two of them so far, from TG Products and from Wico Corp. Here's what I think:

The TG track ball is an Apple-colored box about 5 x 6 x 2 1/2 inches, with a red ball that is a little over two inches in diameter. There are two pushbuttons on the left edge of the box. It plugs into the game port, just like a joystick. It contains two potentiometers and can be used with existing paddle-reading software, also just like a joystick. This ball feels stiff and jerky, and requires a constant downward pressure on the ball to keep the pots properly tracking. The range of values is 0-255, with no wraparound.

$64.95 TG Products, 1104 Summit Ave., #110, Plano, TX, 75074

The Wico track ball is a cream-colored ball in a black and red box, just about the same size as the TG. There are two buttons in the upper left corner of the top side, convenient to the left thumb. The larger button is about .8" in diameter, the other is about .3" across. This unit uses its own interface card (which is supplied), so it leaves the game port free, but requires a motherboard slot.

Wico's ball is based on the same design as the arcade controls: the track ball rolls on ball bearings, and is read by optically counting the revolutions of the rollers supporting the ball. This design gives a much better, smoother feel to the ball's motion, and gives the programmer more flexible ways to read and control the ball. However, it also means that no existing programs can use the Wico ball.

$79.95 Wico Corp., 6400 Gross Point Rd., Niles, IL, 60648

In summary, the TG track ball fits right into the "standard" Apple environment. It plugs into the game port and works with all software that reads paddles 0 and 1, but it feels awkward to use. I consider it a poor substitute for a joystick or paddles, where they are appropriate ... and a poor substitute for a real trackball, if that's what you need.

On the other hand, the Wico track ball is much more responsive and comfortable to use, but it requires an interface slot and special programming. I think it's well worth the effort, and I intend to try using it in as many different applications as I can think of. Wico's trackball comes with a booklet containing a couple of pages about programming with their interface. The following is a summary of that information, plus whatever I've been able to figure out. The program given here reads the ball and displays the X and Y values on the screen in hex notation. It also checks the keyboard for the keys "1" through "4", and sets the ball's speed to match.

The interface card contains no ROM. It does have eight registers which you read and/or write to control the trackball. Here is a table of the registers' addresses and functions ("N" is slot number + 8, i.e., $9-$F):

    Address       Read         Write

     $C0N0     X Position   X Position
     $C0N1     Y Position   Y Position
     $C0N2     Bounded      Bounded
     $C0N3     Wraparound   Wraparound
     $C0N4         -        Speed 1
     $C0N5         -        Speed 2
     $C0N6     Buttons      Speed 3
     $C0N7         -        Speed 4

The first two registers, $C0N0 and $C0N1, contain the X and Y readings from the trackball. You can write to these locations to set starting values, or to force particular values at any time. Lines 1850-2040 of the program show how to read the registers, limit their values, and keep track of current value, last value and change since last reading.

Another approach is to read only the change in value from the trackball, and keep track of the values separately. To do that, first turn on the wraparound feature, as described below. Then set $C0N0 and $C0N1 to 0. That takes care of initial- ization. Now, whenever you want to read the changes in the ball's position, just call this routine:

           LDA XREG
           STA DX
           LDA YREG
           STA DY
           LDA #0
           STA XREG
           STA YREG
           RTS

Since wraparound is permitted, DX and DY will be positive when the ball was moved down or right, and negative when it was moved up or left. Reset the registers to 0 after reading them, and next time you call this routine they will again contain only the change in value.

$C0N2 (BOUNDRY) and $C0N3 (WRAPS) control whether the readouts will stop at 0 and 255, or wrap around. Reading or writing to either address will set the corresponding condition.

You can read the state of the pushbuttons from $C0N6; each button turns on one bit. Bit 7 (sign bit) is the large button and bit 6 (overflow bit) is the small one. These are very easy to test from assembly language; just BIT $C0N6 and use BMI & BPL for the large button, or BVC & BVS for the small one. Lines 2060-2140 of the program show a good way to translate these bits into bytes, so Applesoft can easily test them.

The speed or scale of the readout can be controlled by writing to two of the locations from $C0N4-$C0N7. The values written do not matter, you're throwing soft switches. These addresses select a divider to apply to the X and Y readings. Here's a table of addresses and effects:

      Addresses       Speed    Divide by

    $C0N6 & $C0N4    Fastest      1
    $C0N6 & $C0N5    Med Fast     2
    $C0N7 & $C0N4    Med Slow     4
    $C0N7 & $C0N5    Slowest      8

At the fastest setting, a quarter-turn of the ball produces about a sixteen-point difference in the X or Y reading. At the slowest setting, the same motion changes the readout by two points. Lines 2160-2260 are just a quick way to read the keyboard and produce a value of 0-3. Lines 2280-2390 are how I translate that number into writing the correct pair of addresses.

The track ball has also proved to be an excellent cursor control for graphics work, and a lot of fun to use for controlling menu selection. I'm looking forward to trying it out as a cursor control with the S-C Word Processor.

We don't plan to stock the Track Balls, but if you want one, we can get the Wico unit for you for $75 plus shipping.

      1000 *--------------------------------
      1010 *  READ AND WRITE WICO TRACKBALL INTERFACE
      1020 *--------------------------------
      1030 CH       .EQ $24
      1040 KEYBOARD .EQ $C000
      1050 STROBE   .EQ $C010
      1060 VTABZ    .EQ $FC24
      1070 HOME     .EQ $FC58
      1080 COUT     .EQ $FDED
      1090 PRBYTE   .EQ $FDDA
      1100 *--------------------------------
      1110 *    WICO INTERFACE REGISTERS
      1120
      1130 SLOT    .EQ 4     INTERFACE LOCATION
      1140
      1150 BASE    .EQ SLOT*$10+$C080
      1160 REGS    .EQ BASE+0
      1170 XREG    .EQ BASE+0
      1180 YREG    .EQ BASE+1
      1190 BOUNDRY .EQ BASE+2
      1200 WRAP    .EQ BASE+3
      1210 SPEED1  .EQ BASE+4
      1220 SPEED2  .EQ BASE+5
      1230 SPEED3  .EQ BASE+6
      1240 SPEED4  .EQ BASE+7
      1250 BUTTONS .EQ BASE+6
      1260 *--------------------------------
      1270 SETUP  JSR HOME
      1280        LDA #0
      1290        STA SPEED         START AT TOP SPEED
      1300        STA BOUNDRY       NO WRAPAROUND
      1310        LDY #1            DO THIS TWICE
      1320 .1     LDA HIGH.LIMITS,Y
      1330        SBC LOW.LIMITS,Y  SET INITIAL
      1340        LSR               VALUE TO
      1350        ADC LOW.LIMITS,Y  CENTER OF
      1360        STA LOCATIONS,Y   LIMITS
      1370        STA REGS,Y
      1380        STA LAST.VALUES,Y
      1390        DEY
      1400        BPL .1            DONE?
      1410
      1420 LOOP   LDA #10           CENTER DISPLAY
      1430        JSR VTABZ
      1440        LDA #16           ON SCREEN
      1450        STA CH
      1460        JSR READ.BALL     GO READ BALL
      1470        LDA X
      1480        JSR PRBYTE        SHOW X READING
      1490        LDA #$A0
      1500        JSR COUT
      1510        LDA Y             AND Y READING
      1520        JSR PRBYTE
      1530        JMP LOOP          DO IT AGAIN
      1540 *--------------------------------
      1550 *   VARIABLES
      1560 *--------------------------------
      1570 LOW.LIMITS
      1580 X.LOW  .DA #0       MINIMUM VALUES
      1590 Y.LOW  .DA #0
      1600
      1610 HIGH.LIMITS
      1620 X.HIGH .DA #$FF     MAXIMUM VALUES
      1630 Y.HIGH .DA #$FF
      1640
      1650 LOCATIONS
      1660 X      .DA #0       POINTS TO PLOT
      1670 Y      .DA #0
      1680
      1690 LAST.VALUES
      1700 LAST.X .DA #0       FROM LAST CALL
      1710 LAST.Y .DA #0
      1720
      1730 DELTAS
      1740 DX     .DA #0       CHANGE IN VALUES
      1750 DY     .DA #0
      1760
      1770 S1     .DA #0       LARGE BUTTON
      1780 S2     .DA #0       SMALL BUTTON
      1790
      1800 SPEED  .DA #0       0=FASTEST, 3=SLOWEST
      1810
      1820 *--------------------------------
      1830 READ.BALL
      1840
      1850 SET.X.AND.Y
      1860        LDY #1            DO THIS TWICE
      1870 .1     LDA REGS,Y        READ BALL
      1880        CMP LOW.LIMITS,Y  TOO LOW?
      1890        BCS .2            NO, GO ON
      1900        LDA LOW.LIMITS,Y  YES, FORCE LOW LIMIT
      1910        STA REGS,Y
      1920 .2     CMP HIGH.LIMITS,Y TOO HIGH?
      1930        BCC .3            NO, GO ON
      1940        LDA HIGH.LIMITS,Y YES, FORCE HIGH LIMIT
      1950        STA REGS,Y
      1960 .3     STA LOCATIONS,Y   USE THIS POINT
      1970        PHA               SAVE IT
      1980        SEC
      1990        SBC LAST.VALUES,Y CALCULATE CHANGE
      2000        STA DELTAS,Y
      2010        PLA               RESTORE POINT USED
      2020        STA LAST.VALUES,Y AND SAVE IT FOR NEXT READING
      2030        DEY
      2040        BPL .1            DONE?
      2050
      2060 SET.SWITCHES
      2070        LDA #0
      2080        STA S1       ZERO
      2090        STA S2       READOUTS
      2100        LDA BUTTONS  READ PUSHBUTTONS
      2110        ASL          BIT 7 TO CARRY
      2120        ROL S1         TO S1.
      2130        ASL          BIT 6 TO CARRY
      2140        ROL S2         TO S2.
      2150
      2160 CHECK.KEYBOARD
      2170        LDA KEYBOARD   KEYPRESS?
      2180        BPL EXIT       NO, GO ON
      2190        STA STROBE     YES
      2200        CMP #$B5       >4?
      2210        BCS EXIT       YES, GO ON
      2220        CMP #$B0       <1?
      2230        BCC EXIT       YES, GO ON
      2240        AND #$0F       LOSE HIGH NYBBLE
      2250        SBC #1         MAKE 0-3
      2260        STA SPEED      AND SAVE IT
      2270
      2280 SET.SPEED
      2290        LDA SPEED    GET SPEED
      2300        PHA
      2310        AND #2       USE BIT 1
      2320        LSR          NOW 0 OR 1
      2330        TAY          INDEX
      2340        STA SPEED3,Y HIT SPEED3 OR SPEED4
      2350        PLA          GET SPEED AGAIN
      2360        AND #1       USE BIT 0
      2370        TAY
      2380        STA SPEED1,Y HIT SPEED1 OR SPEED2
      2390 EXIT   RTS
      2400 *--------------------------------
      2410        .LIF

Ampersand Monitor Caller | Bob Sander-Cederlof

A recent issue of the Maple Orchard, magazine published by the Loyal Ontario Group Interested in Computers (LOGIC, P. O. Box 696 station B, Willowdale, Ontario, Canada M2K 2P9) was entirely devoted to assembly language. Bob Stitt, also one of our readers, authored an article called "A New Utility for Applesoft".

Bob's new utility provided a way to execute monitor commands from inside a running Applesoft program. He implemented the S.H.Lam method in machine language as an ampersand routine.

A long long time ago someone named S. H. Lam showed the world a neat way to execute monitor commands from Applesoft. His method was published by Call APPLE back about 1978, I think. Lam POKEd the characters of a string containing a monitor command into the monitor's input buffer at $200. He included " N D9C6G" at the end of each command string, to return control to Applesoft. Then POKE 72,0:CALL-144 executes the command.

         100 C$="300:A9 3A 20 C0 DE 60 N D9C6G"
         110 FOR I=1 TO LEN(C$)
         120 POKE 511+I, ASC ( MID$ (C$,I,1)) + 128
         130 NEXT
         140 POKE 72,0 : CALL-144

Bob Stitt's utility replaces lines 110-140 above with a simple ampersand statement:

         110 & C$

After reading the article, I decided to try writing my own. I came up with a different technique; it is probably no better, but it is bigger. Mine works like a similar routine coded by Steve Wozniak inside the mini-assembler in the Integer BASIC ROMs. The only advantage I find is that there is no need to append " N D9C6G" to each command string. As a result, you can execute "3D0G" (if you like) and stop execution of your Applesoft program.

Lines 1220-1280 set up the ampersand vector. You can BLOAD the program and CALL 768 to run these lines, or simply BRUN it.

Line 1310 clears the monitor MODE, so that it realizes it is at the beginning of a command.

Lines 1320 and 1330 set up the string which follows the ampersand. The length will be in the A-register, and the address of the first character in INDEX ($5E,$5F). Lines 1340-1420 copy the string data into the monitor's buffer at $200. The characters are moved in backwards order, after first storing a carriage return at the end. So far the code is very similar to that of Bob Stitt.

Lines 1440-1590 are very similar to code found in the mini-assembler (at $F538-$F559 in the Integer BASIC ROMs). MON.GETNUM parses one hexadecimal number, if present, and then returns with a modified form of the first non-hex character in the A-register. Lines 1480-1520 search the monitor's command table for a matching character. If none is found, you will hear a bell. If found, the carriage return command is a special case. Lines 1540-1590 handle the carriage return command, and lines 1440-1450 handle all the others.

When the command has been fully parsed and executed, control will return to your Applesoft program. That is, unless your command had the effect of aborting Applesoft. Here is a sample program:

         10 PRINT CHR$(4)"BLOAD B.MONITOR" : CALL 768
         20 INPUT C$
         30 & C$
         40 PRINT : GOTO 20

Note that to type in a command which contains a ":" you will have to type a leading quotation mark. Otherwise Applesoft will issue its "EXTRA IGNORED" message and truncate your input at the colon.

Inside your Applesoft program you can build the command string using any sort of string functions and concatenation you wish.

      1000 *SAVE S.AMPER MONITOR
      1010 *--------------------------------
      1020 *      &MONITOR
      1030 *--------------------------------
      1040 AMPERSAND.VECTOR .EQ $3F5
      1050 AS.FRMEVL  .EQ $DD7B
      1060 AS.FRESTR  .EQ $E5FD
      1070 *--------------------------------
      1080 MON.MODE   .EQ $31
      1090 MON.YSAV   .EQ $34
      1100 INDEX      .EQ $5E,5F
      1110 *--------------------------------
      1120 BUFFER     .EQ $200
      1130 MON.TOSUB  .EQ $FFBE
      1140 MON.GETNUM .EQ $FFA7
      1150 MON.CHRTBL .EQ $FFCC
      1160 MON.BELL   .EQ $FF3A
      1170 MON.BL1    .EQ $FE00
      1180 MON.ZMODE  .EQ $FFC7
      1190 *--------------------------------
      1200        .OR $300
      1210 *--------------------------------
      1220 SETUP  LDA #$4C     JMP OPCODE
      1230        STA AMPERSAND.VECTOR
      1240        LDA #FAKE.MONITOR
      1250        STA AMPERSAND.VECTOR+1
      1260        LDA /FAKE.MONITOR
      1270        STA AMPERSAND.VECTOR+2
      1280        RTS
      1290 *--------------------------------
      1300 FAKE.MONITOR
      1310        JSR MON.ZMODE
      1320        JSR AS.FRMEVL
      1330        JSR AS.FRESTR
      1340        TAY
      1350        LDA #$8D     <RETURN>
      1360 .1     STA BUFFER,Y
      1370        TYA
      1380        BEQ FMN2     END OF STRING
      1390        DEY
      1400        LDA (INDEX),Y
      1410        ORA #$80
      1420        BNE .1       ...ALWAYS
      1430 *--------------------------------
      1440 FMN1   JSR MON.TOSUB
      1450        LDY MON.YSAV
      1460 FMN2   JSR MON.GETNUM
      1470        STY MON.YSAV
      1480        LDY #22
      1490 .1     CMP MON.CHRTBL,Y
      1500        BEQ .2
      1510        DEY
      1520        BPL .1
      1530        JMP MON.BELL
      1540 .2     CPY #21
      1550        BNE FMN1
      1560        LDA MON.MODE
      1570        LDY #0
      1580        DEC MON.YSAV
      1590        JMP MON.BL1

Apple Assembly Line is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $15 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $13 postage for other countries. Back issues are available for $1.50 each (other countries add $1 per back issue for postage).

All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)