Apple Assembly Line
Volume 3 -- Issue 2 November 1982

In This Issue...

Apple/Fest in Houston

Although only about one-third the size of the Boston original (last May), it was still worth the trip. I met an orthopedic surgeon from Lille, France, who flew down Saturday from New York just for the show. Also a professor from Des Moines. There was not a lot to see that could be called NEW, but it was valuable to meet and get to know the people. I brought along my son David, almost ten now; he loved playing all the new games, and was a big help in the IAC booth.

If you are in an area where there is no club of Apple owners, you might like to contact the International Apple Core at 908 George St, Santa Clara, CA 95050. They have a start-up kit for new clubs that will help you organize your own club.

Bill Morgan also came on Saturday. We have kidded Bill in the past that he looked a lot like Paul Lutus...well, three people were almost positive on Saturday!

Another Christmas Special

And this one is in December! Subscribers have until the end of 1982 to get Laumer Research's FLASH Integer Basic Compiler at only $49. That is a savings of nearly 38%!


A Sight of Sound Herbert A. & Herbert L. McKinstry

The Apple-Talker program that came on our disk for the S-C Assembler II Version 3.2 does some interesting things that go beyond what it was designed to do. When we tried it out we played a recorded message from our cassette recorder into the Apple memory and were amazed at the computer rendering of the original words.

The actual quality of reproduction leaves something to be desired, so when someone said "Let's see what it sounds like," and another said "Let's hear what it looks like," we snooped around the program listing and found that what we were hearing was stored on Hi-Res graphics page one. We looked at it by typing in $C050:0 and $C057:0. The sight of the sound was not too loud, nor was it even obvious that what we were looking at was the sound that we had heard.

If there were a pitch there, we should see some kind of pattern. We recorded a pitch, and saw that the sound was noisy. So then we entered some sense into memory by creating a repeating pattern, and listened to the patterns. We tried some like this:

     *2000:FF FF 00 00 N 2004<2000.27FFM
     *2800:FF 00 N 2802<2800.2FFFM
     *3000:F0 N 3001<3000.37FFM
     *3800:CC N 3801<3800.3FFEM

        and

     *2000:FC 0F C0 00 N 2004<2000.27FFM
     *2800:F8 3F 03 E0 N 2804<2800.2FFFM
     *3000:AA N 3001<3000.37FFM
     *3800:CC N 3801<3800.3FFEM

We liked what we saw and we saw what we heard, so our thanks to Bob Sander-Cederlof and to Victor Borge for his recognition of the sight of sound.


Your Apple Can Talk Bob Sander-Cederlof

Back in the summer of 1978, I spent two weeks in California with my kids. I visited a couple of computer stores with my brother, to show him what my Apple looked like. In one of them, I think the Byte Shop in Westminster on Beach Blvd., the proprietor mentioned in passing an astonishing event. He told me, "A high schooler was in here a few weeks ago with a program that produced speech out of the Apple speaker!" "Impossible," I mumbled.

A few weeks later I heard rumors of a program by Bob Bishop which did indeed make the Apple talk. I think it was in the September meeting of the Dallas Apple Corps that I overheard his program running. From amazement to insight took only a few seconds...I almost RAN home to write a program to do the same thing!

A month or two later I handed out copies of the program and gave a talk on the subject of speech synthesis and recognition. When Version 3.2 of the S-C Assembler was released, I included the same program as an example. Then again on version 4.0

Meanwhile, Bob Bishop released several neat tapes through Softape, including a talking calculator, "Apple Talker", and "Apple Listener". The latter program did some limited speech recognition through the cassette port, with no additional hardware. I bought the last two, and I still have the tape somewhere, but I have never loaded it.

About two years later Muse Software started marketing a program on disk to evoke speech from the Apple. I believe they included some sort of "editor" to allow you to make your own programs talk. I never saw or heard it, so I don't know.

As far as I know, the basic idea behind all of these programs is the same: approximate the waveform of spoken words by toggling the Apple speaker. You can hook a microphone up to the cassette input port and toggle the output speaker whenever the input port changes. Or you can record a message on tape, and "play" it into the Apple.

My program samples the cassette input port about 6000 times per second. If the input byte is $80 or larger, I store a "1"; if less than $80, I store a "0". I pack eight bits in a byte, and store the bytes in a buffer from $4000 through $5FFF. The buffer is 8192 bytes long, so that is 65536 samples or about 10 seconds of stored sound. You could store more samples or less samples, according to your own needs.

The playback loop looks at the stored bits at the same rate, and toggles the speaker whenever there is a change from 1 to 0 or 0 to 1. The result is actually understandable, though somewhat scratchy.

As the McKinstry's pointed out, my choice of buffer coincides with the Hi-Res Graphics page. In the copy of the program they have, I used $2000-3FFF, which is Hi-Res page one. Now I use $4000-5FFF, Hi-Res page two, so it will not erase the last half of the S-C Assembler when I test the program. Taking their suggestions to heart, I added the code to turn on the Hi-Res display during recording and playback, and to turn it off when finished.

When looking at the display you need to bear in mind the complex way the bytes are arranged on the Hi-Res screen. For starters, the bits are backwards in each byte. And remember that only seven bits of each byte show up on the screen -- the 8th bit shifts the other seven one half dot position. And the big confuser is the way the lines are arranged. (See Mike Laumer's article in the July 1982 issue of AAL, page 15ff, for a discussion of the line arrangement.)

I prepared some EXEC files which initialize the buffer to various patterns, including the ones the two Herberts suggested. (It is amazing how handy it is to be able to create/modify little text files like these using the editor in the S-C Macro Assembler!)

   Sound #1

     $4000:FF FF 00 00 N 4004<4000.47FFM
     $4800:FF 00 N 4802<4800.4FFFM
     $5000:F0 N 5001<5000.57FFM
     $5800:CC N 5801<5800.5FFEM

   Sound #2

     $5800:CC N 5801<5800.5FFEM
     $5000:AA N 5001<5000.57FEM
     $4800:F8 3F 03 E0 N 4804<4800.4FFCM
     $4000:FC 0F C0 00 N 4004<4000.47FCM

   Sound #3

     $4000:00 01 03 07 0F 1F 3F 7F FF FE FC F8 F0 E0 C0 80
     $4010<4000.5FEFM

   Sound #4

     $4000:00 FF 00 00 FF FF 00 00 00 00 FF FF FF FF
     $400E<4000.5FFFM

   Sound #5

     $4000:00 88 00 00 88 88 00 00 00 00 88 88 88 88
     $400E<4000.5FFFM

To play back one of the sounds above, simply EXEC or type in the monitor commands, and then "MGO TALK".

Looking at the program which follows, you find three main routines. ECHO (lines 1180-1300) samples the cassette port about 6000 times per second; if it has changed, the speaker is toggled. After each toggle the keyboard strobe is examined, so that typing any key can stop the program and return to the caller.

RECORD (lines 1560-1710) stores 65536 samples in the buffer. TALK (lines 1750-1950) play back the buffer contents. You can play with the sample rate and playback rate by modifying the constant 30 in lines 1590 and 1790. It is amusing to play back a message faster or slower than it was recorded.

Both RECORD and TALK use a monitor subroutine called NXTA to control the loop. This is the same subroutine used by the monitor memory display and memory move commands. NXTA tests the current value of A1L,A1H ($3C,$3D) against A2L,A2H ($3E,$3F), and sets carry if A1 is greater than or equal to A2. Then it increments A1.

I tried various schemes for packing the bits in the buffer, to save space for more speech. None of them were effective enough to bother with, but you might run on to one that is. I also experimented with isolating words and individual phonemes, and with trying to filter out the scratchiness. I was not satisfied with any of my results. If you are successful, I would like to hear about it.

[ A later note: I just received Dec-82 Creative Computing, and there are reviews of several speech synthesis systems. One, called "Software Automatic Mouth (SAM)", is claimed to be a "high quality speech synthesizer created entirely in software." SAM costs $125 ($99 from Huntington Computing from now until the end of the year). In spite of the claim, it is not entirely software. There is also a small board containing a digital-to-analog converter (DAC), an audio amplifier, and a volume control. You can hook it up to the speaker in the Apple, or supply an external speaker. The ad claims it enables you to add speech to your programs with ease, but bear in mind that the software takes 9K of RAM, and 6K more if you want to automatically translate straight English text to speech. ]

 1000  *--------------------------------
 1010  *       APPLE-TALKER FROM S-C SOFTWARE CORP.
 1020  *--------------------------------
 1030  MON.NXTA .EQ $FCBA     BUMP AND TEST A1
 1040  *--------------------------------
 1050  CASSETTE .EQ $C060     CASSETTE INPUT LEVEL
 1060  SPEAKER  .EQ $C030     SPEAKER OUTPUT
 1070  STROBE   .EQ $C010
 1080  KEYBOARD .EQ $C000
 1090  *--------------------------------
 1100  LAST     .EQ $2F       LAST CASSETTE INPUT LEVEL
 1110  A1L      .EQ $3C       MONITOR A1L, A1H, A2L, A2H
 1120  *--------------------------------
 1130  BUFFER   .DA $4000     FWA OF BUFFER
 1140           .DA $5FFF     LWA OF BUFFER
 1150  *--------------------------------
 1160  *    ECHO CASSETTE THRU SPEAKER
 1170  *--------------------------------
 1180  ECHO   LDY #30       150 USEC DELAY
 1190  .1     DEY
 1200         BNE .1
 1210         LDA CASSETTE
 1220         EOR LAST      SEE IF TOGGLED
 1230         BPL ECHO      NO
 1240         EOR LAST      YES
 1250         STA LAST
 1260         LDA SPEAKER   TOGGLE SPEAKER
 1270         LDA KEYBOARD
 1280         BPL ECHO
 1290         STA STROBE
 1300         RTS
 1310  *--------------------------------
 1320  *    SET UP BUFFER ADDRESSES
 1330  *--------------------------------
 1340  SETUP  LDX #3
 1350  .1     LDA BUFFER,X
 1360         STA A1L,X
 1370         DEX
 1380         BPL .1
 1390         STX LAST
 1400         LDA $C050    SELECT HGR2 FOR VIEWING
 1410         LDA $C052
 1420         LDA $C055
 1430         LDA $C057
 1440         RTS
 1450  *--------------------------------
 1460  *    RESTORE NORMAL SCREEN AND EXIT
 1470  *--------------------------------
 1480  FINISH LDA $C051
 1490         LDA $C053
 1500         LDA $C054
 1510         LDA $C056
 1520         RTS
 1530  *--------------------------------
 1540  *    STORE SPEECH IN BUFFER
 1550  *--------------------------------
 1560  RECORD JSR SETUP     SET UP BUFFER ADDRESSES
 1570  .1     LDX #8        EIGHT BITS
 1580  .2     PHA           PUSH BYTE WE ARE FILLING
 1590         LDY #30
 1600  .3     DEY           150 USEC DELAY
 1610         BNE .3 
 1620         LDA CASSETTE  READ CASSETTE LEVEL
 1630         ASL           LEVEL INTO CARRY BIT
 1640         PLA
 1650         ROL           MERGE LEVEL INTO BYTE
 1660         DEX
 1670         BNE .2        BYTE NOT FULL YET
 1680         STA (A1L,X)   STORE NEXT WORD IN BUFFER
 1690         JSR MON.NXTA      BUMP & TEST POINTER
 1700         BCC .1        NOT THRU
 1710         JMP FINISH
 1720  *--------------------------------
 1730  *    PLAYBACK SPEECH FROM BUFFER
 1740  *--------------------------------
 1750  TALK   JSR SETUP     SET UP BUFFER ADDRESSES
 1760  .1     LDX #0
 1770         LDA (A1L,X)   GET NEXT WORD FROM BUFFER
 1780         LDX #8        EIGHT BITS
 1790  .2     LDY #30
 1800  .3     DEY           150 USEC DELAY
 1810         BNE .3 
 1820         EOR LAST      TEST IF LEVEL CHANGED
 1830         BPL .5        NO
 1840         EOR LAST      YES, RESTORE (A)
 1850         STA LAST      UPDATE LEVEL
 1860         LDY SPEAKER   TOGGLE SPEAKER
 1870  .4     ASL
 1880         DEX
 1890         BNE .2 
 1900         JSR MON.NXTA      BUMP & TEST POINTER
 1910         BCC .1        NOT THRU
 1920         JMP FINISH
 1930  .5     EOR LAST      RESTORE (A)
 1940         JMP .6        EVEN OUT TIMING
 1950  .6     JMP .4

Speaking of Speech Bill Morgan

Just thought I'd tell you a little about the way I played around with a speech program like Bob's. I couldn't find the disk with the exact code, but here's what I remember about it. I wanted a brief Applesoft program which would say the numbers 0 through 9 when a number key was pressed.

To do this, first record your voice on tape, reciting the ten numbers. Then play the tape into your Apple, using the RECORD routine in Bob's program. Now, by using the system monitor to examine memory, it's easy to scan through the buffer and see where each word begins and ends. The gaps between words will be long stretches of "00 ... 00", with a few stray bytes of noise along the way. Words will be stretches of random-looking values. It's interesting to see the difference between a word like "two", which starts abruptly and trails off, and one like "eight", which starts more slowly and ends suddenly.

Now you can use the monitor move command to remove the gaps between words. Move the data for "one" to the very beginning of the buffer, and note its start and end addresses. Then move "two" down to the space just after "one", and note the addresses. Carrying on like this, you can compress the number data into about half the space of the original recording.

Assemble the playback portion of Bob's program at $300. All you should need is lines 1760-1950 (plus the needed .EQ's), with an RTS substituted for the JMP FINISH at line 1920. To say a number, all your Applesoft program has to do is get the starting and ending addresses of a word from an array, POKE the addresses into locations 60-63, and CALL 768.


Even Faster Primes Anthony Brightwell

Is this the last word on prime number generation?

I modified Charles Putney's program from the February issue, and cut the time from 330 milliseconds down to 183 milliseconds! Here is what I did:

The method I use for squaring may appear very round-about, but it actually is faster in this case. Look at the following table:

    Odd #'s  square   neat formula
       1       1       0 * 8 + 1
       3       9       1 * 8 + 1
       5      25       3 * 8 + 1
       7      49       6 * 8 + 1
       9      81      10 * 8 + 1

The high byte of the changing factor in the "neat formula" is stored in the LDA instruction at line 1550, and the low byte in the ADC instruction at line 1900. The factor is the sum of the numbers from 1 to n: 1+2=3, 1+2+3=6, 1+2+3+4=10, etc. In all, 31 primes are squared, and the total time for all the squaring is less than 3 milliseconds.

Here is a driver in Applesoft to load the program and then print out primes from the data array.

     10  REM DRIVER FOR TONY'S FAST PRIME FINDER
     20  PRINT CHR$ (4)"BLOAD B.TONY'S SUPER-FAST PRIMES"
     30  HOME : PRINT "HIT ANY KEY TO START"
     40  GET A$: PRINT " GENERATING PRIMES . . ."
     50  CALL 32768
     60  FOR A = 8195 TO 24576 STEP 2
     70  IF  PEEK (A) = 0 THEN  PRINT A - 8192;" ";
     80  NEXT

A few more cycles can probably still be shaved.... Any takers?

 1000  *SAVE S.TONY'S SUPER-FAST PRIMES
 1010         .OR $8000    SAFELY OUT OF WAY
 1020         .TF B.TONY'S SUPER-FAST PRIMES
 1030  *---------------------------------
 1040  BASE   .EQ $2000    BASE OF PRIME ARRAY
 1050  BEEP   .EQ $FF3A    BEEP THE SPEAKER
 1060  *--------------------------------
 1070         .MA ZERO
 1080         STA ]1+$001,X
 1090         STA ]1+$101,X
 1100         STA ]1+$201,X
 1110         STA ]1+$301,X
 1120         STA ]1+$401,X
 1130         STA ]1+$501,X
 1140         STA ]1+$601,X
 1150         STA ]1+$701,X
 1160         .DO ]1<$5800
 1170         >ZERO ]1+$800
 1180         .FIN
 1190         .EM
 1200  *---------------------------------
 1210  *      MAIN CALLING ROUTINE
 1220  *
 1230  MAIN   LDA #100     DO 100 TIMES SO WE CAN MEASURE
 1240         STA COUNT    THE TIME IT TAKES
 1250         JSR BEEP     ANNOUNCE START
 1260  .1     JSR PRIME
 1270         DEC COUNT    CHECK COUNT
 1280         BNE .1       DONE ?
 1290         JMP BEEP     SAY WE'RE DONE
 1300  *---------------------------------
 1310  *      PRIME ROUTINE
 1320  *      SETS ARRAY STARTING AT BASE
 1330  *      TO $FF IF NUMBER IS NOT PRIME
 1340  *      CHECKS ONLY ODD NUMBERS > 3
 1350  *      INC = INCREMENT OF KNOCKOUT
 1360  *      N = KNOCKOUT VARIABLE
 1370  *--------------------------------
 1380  PRIME
 1390         LDX #1
 1400         STX SHCNT+1  STARTING MULTIPLIER FOR SQUARE
 1410         STX MULT+1
 1420         DEX
 1430         STX SQUARE+1
 1440         TXA          CLEAR WORKING ARRAY
 1450  .1     >ZERO BASE
 1460         INX          EVERY ODD LOCATION
 1470         INX
 1480         BEQ .2
 1490         JMP .1       NOT FINISHED CLEARING
 1500  *--------------------------------
 1510  .2     LDA #3
 1520         STA START+1
 1530  MAINLP ASL          INC = START * 2
 1540         STA INC+1
 1550  SQUARE LDA #*-*     MOVE MULT TO N
 1560         STA N+2
 1570         LDA MULT+1
 1580         ASL          MULTIPLY BY 8
 1590         ROL N+2
 1600         ASL
 1610         ROL N+2
 1620         ASL
 1630         ROL N+2
 1640         TAX
 1650         INX          AND ADD 1
 1660         BNE .1
 1670         INC N+2
 1680  .1     CLC          ADD BASE TO N
 1690         LDA N+2
 1700         ADC /BASE
 1710         STA N+2
 1720         TAY
 1730         TXA
 1740  LOOP
 1750  N      STA $FF00,X  REMEMBER THAT N IS REALLY AT N+2
 1760  INC    ADC #*-*     N = N + INC
 1770         TAX
 1780         BCC LOOP     DONT'T BOTHER TO ADD, NO CARRY
 1790         INY          INC HIGH ORDER
 1800         STY N+2
 1810         CPY /BASE+$4000  IF IS GREATER THAN $6000
 1820         BCC LOOP     NO, REPEAT 
 1830  START  LDX #*-*     GET OUR NEXT KNOCKOUT 
 1840  NEXT   INX
 1850         INX          START = START + 2
 1860         BMI END      WE'RE DONE IF X>$7F
 1870         INC SHCNT+1  INCREMENT SQUARE MULTIPLIER
 1880  SHCNT  LDA #*-*     AND ADD TO MULTIPLIER
 1890         CLC
 1900  MULT   ADC #*-*
 1910         STA MULT+1
 1920         BCC .1
 1930         INC SQUARE+1
 1940  .1     LDA BASE,X   GET A POSSIBLE PRIME
 1950         BNE NEXT     THIS ONE HAS BEEN KNOCKED OUT
 1960         STX START+1
 1970         TXA
 1980         BNE MAINLP   ...ALWAYS
 1990  END    RTS 
 2000  *--------------------------------
 2010  COUNT  .DA #*-*     COUNT FOR 100 TIMES LOOP

Moving the Symbol Table Bill Morgan

Do you use the language card version of the S-C Macro Assembler? Have you ever tried to create more space for your object code by patching $D01D to move the symbol table up from $1000? Got a MEM PROTECT ERROR, didn't you? Here's what went wrong, and how to fix it.

The problem is the private label table for macros. This table is also protected during assembly, and starts at $FFF and grows downward. The base of the table is defined by a LDA #$10 instruction at $E564. When the table is searched during assembly, the check for the end of the table is a CMP #$10 at $E6A0. Both of these must also be patched to allow the $D01D patch to work. Here are the commands to correct the assembler:

     :$C083 C083 N E564:A5 4B N E6A0:C5 4B N C080
     :BSAVE S-C.ASM.MACRO.LC,A$D000,L$231F

This changes the LDA #$10 to a LDA LOMEM+1 and the CMP #$10 to a CMP LOMEM+1. Now, whenever you want to move the symbol table, just type the following (where XX is the page you want the tables to start with):

     :$C083 C083 D01D:XX N C080
     :NEW

The NEW command is necessary to reset the page-zero pointers.

If you are using a target file and don't care about object code space, you can move the symbol table down. This creates more source code and symbol table space. You can move the table base all the way down to $800, if you are not using private labels. If you are using them, remember that each private label occurence uses 5 bytes of table space, so be sure to leave enough room under the table base.

Here's a map that shows how things got this way:

      -----------
     |   Symbol  |
     |   Table   |
      -----------  LOMEM
     |           |            ----------
     | Assembler |           |   Symbol |
     |           |           |   Table  |
      -----------  $1000      ----------  LOMEM
     |  Private  |           |  Private |
     |  Labels   |           |  Labels  |
      -----------             ----------

        Normal               Language Card
        Version                 Version

The normal version of the assembler has to start at $1000, so the private label table also has to be there. The language card version didn't get changed to reflect the fact that the private labels could now be moved.


EXEC without END from Applesoft Bob Sander-Cederlof

I have been working on a project with Lee Meador which requires a binary file to be loaded into the second $D000 bank of a 16K RAM card. It is just a little tricky to do this!

You cannot just use a simple BLOAD, because you have to be sure the RAM card is selected and write-enabled. You cannot do it from a running Applesoft program, or even as a direct command after the Applesoft prompt, because if the RAM card is enabled the Applesoft ROMs are not. We wanted to do it from within the running Applesoft program.

The typical answer is to create an EXEC file with the commands to call the monitor, select the RAM card, BLOAD the file, reselect the motherboard ROMs, and bounce back to Applesoft. For example:

     CALL-151               call Apple monitor
     C089 C089              write-enable RAM with 2nd bank
     F800<F800.FFFFM        copy of monitor in RAM card
     BLOAD B.BOBANDLEE      load the file
     C081                   back to Applesoft ROMs
     3D0G                   back to Applesoft, softly

You can nicely EXEC this file from the direct mode, or from a running Applesoft program. However, in order to use it from a running program, the program must END or STOP. Do it like this:

     100 PRINT CHR$(4)"EXEC LOAD 2ND BANK":END

If you don't END the program, the EXEC file will probably just become part of the input to your Applesoft program, rather than being executed.

HOWEVER.... You can beat the system. Change the EXEC file to this form:

     C089 C089
     F800<F800.FFFFM
     BLOAD B.BOBANDLEE
     C081
     D7D2G

And the Applesoft code to this:

     100 PRINT CHR$(4)"EXEC LOAD 2ND BANK":CALL-151

Note the two changes in the EXEC file: the CALL-151 is not there, and 3D0G has become D7D2G. And in the Applesoft code instead of END we have CALL-151.

The CALL-151 starts up the Apple monitor, which reads the commands from the EXEC file. The last command jumps to $D7D2, the running entry into Applesoft. This continues execution of the Applesoft program from the next statement after the CALL-151.


Applesoft Program Locator Bill Morgan

Have you ever wanted to know exactly where your Applesoft program and variables are in memory? How much space is code and how much is variables? How close you're getting to the Hi-Res display space? FRE(0) will tell you how much space you have, but not where it is. You can PEEK the Applesoft pointers, or go into the monitor to check them, but that means you have to remember where all the pointers are.

In the October, 1982 issue of Big Apple Users Digest I saw a program by Frank Weinberg to build an EXEC file called FPSTAT, which displays the Applesoft pointers to program and variable locations. (That program was credited as being reprinted from The Grapevine, August, 1982.) Now that was pretty neat, but EXEC is so slow, and the adresses were printed in decimal. I'm more comfortable thinking of addresses in hex notation. Bob suggested writing a BRUNnable program which would execute in page 2 (the input buffer), thus avoiding conflict with any page 3 routines that might be present. Here's what I came up with.

Using LOCATOR

Whenever you want to know the memory situation, just BRUN LOCATOR. It will display something like this:

          PROGRAM: $0801 TO $0923
           SIMPLE: $0923 TO $0A35
           ARRAYS: $0A35 TO $1B3C
          STRINGS: $9435 TO $9600

PROGRAM shows the location of the actual text of your program. SIMPLE is the simple variables, both numeric and string pointers. ARRAYS is the array variables, both numeric and string. STRINGS is the area used by the actual text of the strings.

Notice that the upper addresses are all one too large. Applesoft's end-of-program and end-of-variables pointers actually point to the next available location, rather than the last location used. Similarly, the end-of-strings pointer is HIMEM, which is one past the last location available. I wrote another version of LOCATOR which automatically decremented the second address in each line, but that got cumbersome, and returned silly values if the Applesoft program had not yet been RUN. (For example, SIMPLE: $0923 TO $0922.)

If you want to CALL LOCATOR from within an Applesoft program, change line 1320 from JMP $3D0 to RTS, and change the origin to $294. Then you can CALL 660, if you're not using very long input lines. Or, you can put LOCATOR in page 3, if you're not already using that area.

It is also interesting to RUN a program, BRUN LOCATOR, then type FRE(0) and call LOCATOR again. This lets you see just how much wasted string space you have had, and gives you some idea how long the garbage collector takes to clear how much space.

I'm looking forward to using LOCATOR together with EXAMINER (from AAL June, 1982) to study Applesoft's variable structure. You can find more information on Applesoft variables and their pointers on pages 126-127 and 137 of the Applesoft manual.

How LOCATOR Works

Since we are printing eight addresses, the X-register is used to count from 0-7. In lines 1140-1190 that count is converted into a value of $0, $8, $10, or $18, to determine which title line to print. If the titles hadn't been a convenient 8 bytes long, we could have inserted a title offset at the beginning of each of the .DA statements in lines 1570-1600, and loaded Y from there.

The heart of the program is the table of Applesoft pointers at lines 1570-1600. In lines 1420-1440 the Y-register is loaded with a value from the table, then used to load the A- and X-registers with the address pointed to. The program then calls MON.PRNTAX, which displays first the A- and then the X-register.

 1000  *SAVE S.LOCATOR
 1010  *--------------------------------
 1020         .OR $292     HIGH END OF INPUT BUFFER
 1030  *      .TF LOCATOR
 1040  *--------------------------------
 1050  ZERO       .EQ 0
 1060   
 1070  MON.PRNTAX .EQ $F941
 1080  MON.COUT   .EQ $FDED
 1090  MON.CROUT  .EQ $FD8E
 1100  *--------------------------------
 1110  START  LDX #0
 1120   
 1130  LOOP   JSR MON.CROUT     NEW LINE
 1140         TXA
 1150         LSR               MAKE (X)
 1160         ASL               INTO
 1170         ASL               TITLE
 1180         ASL               INDEX
 1190         TAY
 1200  .1     LDA TITLES,Y      SHOW TITLE
 1210         JSR MON.COUT
 1220         INY
 1230         CMP #':+$80       ":" ?
 1240         BNE .1
 1250   
 1260         LDY #1            FILL WITH " $"
 1270         JSR PRINT.ADDRESS
 1280         LDY #4            FILL WITH " TO $"
 1290         JSR PRINT.ADDRESS
 1300         CPX #7            DONE YET?
 1310         BCC LOOP          NO, GO ON
 1320         JMP $3D0          YES, EXIT TO DOS
 1330  *--------------------------------
 1340  PRINT.ADDRESS
 1350  .1     LDA FILLER,Y      Y TELLS HOW
 1360         JSR MON.COUT      MUCH FILLER
 1370         DEY               TO PRINT
 1380         BPL .1
 1390   
 1400         TXA
 1410         PHA               SAVE X
 1420         LDY TABLE,X       GET POINTER
 1430         LDA ZERO+1,Y      GET HIGH BYTE
 1440         LDX ZERO,Y        GET LOW BYTE
 1450         JSR MON.PRNTAX    DISPLAY ADDRESS
 1460         PLA
 1470         TAX               RESTORE X
 1480         INX               AND GET READY
 1490         RTS               FOR NEXT PASS
 1500  *--------------------------------
 1510  TITLES
 1520         .AS -/PROGRAM:/
 1530         .AS -/ SIMPLE:/
 1540         .AS -/ ARRAYS:/
 1550         .AS -/STRINGS:/
 1560  *--------------------------------
 1570  TABLE  .DA #$67,#$AF     START OF PROGRAM, END OF PROGRAM
 1580         .DA #$69,#$6B     START OF VARIABLES, START OF ARRAYS
 1590         .DA #$6B,#$6D     START OF ARRAYS, END OF NUMERICS
 1600         .DA #$6F,#$73     START OF STRINGS, HIMEM
 1610  *--------------------------------
 1620  FILLER .AS -/$ OT /

REPEAT and UNTIL for Applesoft Bobby Deen

The following program adds three statements to Applesoft: &REPEAT, &UNTIL, and &POPR. With these you can write Pascal-like loops in your Basic programs.

You start the loop with &REPEAT, and end it with &UNTIL <exp>. The loop will be repeated until the <exp> evaluates to non-zero (true). As long as the value of <exp> is zero (false), the loop will keep going.

I use the system stack for saving the line number and the program pointer, just like Applesoft does with FOR-NEXT loops. A special code is used to identify the stuff on the stack, so you can have FOR-NEXT loops inside REPEAT-UNTIL loops and vice versa.

The statement &POPR removes one REPEAT block from the stack, in case you want to jump out of a loop rather than completing it. (This is not generally a good practice, even with FOR-NEXT loops, but you can do it if you feel you must.) The statement "&UNTIL 1" will do the same thing as &POPR, but &POPR takes less space and time.

If &POPR or &UNTIL is executed when there is not an UNTIL block on the top of the stack, you will get "NEXT WITHOUT FOR" error.

Applesoft parses the word "REPEAT" as four letters "REPE" and the token "AT". This makes the listings look weird, but never mind. Likewise, "UNTIL" looks like a variable name during tokenization, so the expression runs into the letter "L"; but at execution time all is understood.

Here is a sample program which shows a pair of REPEAT loops:

     100  REM TEST REPEAT/UNTIL
     110 D$ =  CHR$ (4): PRINT D$"BLOAD B.REPEAT/UNTIL": CALL 768
     120 I = 0: & REPE AT
     130 I = I + 1: PRINT I":  ";
     135 J = 0: & REPE AT :J = J + 1: PRINT J" ";: & UNTILJ > 14:
         PRINT
     140  & UNTILI = 10

Lines 1200-1250 install the ampersand vector. I assumed the JMP opcode is already stored at $3F5, since DOS does that. After BLOADing the file, CALL 768 will executed these lines.

When the "&" is executed, the 6502 jumps to AMPER.PARSE at line 1270. Lines 1270-1420 search through a table of keywords, matching one if possible with the characters after the "&" in your Applesoft program. This is a general routine, which you can use for any keywords, just by making the appropriate entries in the table (lines 1500-1590).

The table contains a string and an address for each keyword. The string is shown as a hex string, and includes the exact hexadecimal values expected. For example, for "REPEAT" I have entered the ASCII codes for "REPE" and the token value for "AT". After the keyword there is a 00 byte to flag the end, and a two byte address. The address will be pushed onto the stack so that an RTS instruction will branch to the processing program for that keyword. Since RTS adds one, the address in the table have "-1" after them.

The last entry in the table has a null keyword, so it will match anything and everything. If the search goes this far, we have a syntax error; therefore the branch address is to the Applesoft syntax error code.

When a keyword is matched, the Y-register contents need to be added to TXTPTR. A subroutine in the Applesoft ROMs does this, called AS.ADDON. Since both REPEAT and POPR require the next character to be end-of-line or a colon, a JMP to AS.CHRGOT gets the next character and tests it. The RTS at the end of AS.CHRGOT actually branches to the processing code for the keyword.

Lines 1600-1840 process the REPEAT command. A five-byte block is pushed onto the stack, consisting of the current line number, the TXTPTR, and a code value $B8.

Lines 1850-2070 process the UNTIL command. First the expression is evaluated. If the value turns out to be zero, the byte at FAC.EXP will be zero. If it is zero, we need to keep looping; if non-zero, the loop is finished. Looping involves copying the line number and text pointer from the stack back into CURLIN and TXTPTR, and then going to AS.NEWSTT. The REPEAT block is left on the stack, and execution resumes just after the &REPEAT that started this loop.

If the expression is true (non-zero), the loop is terminated. Termination is trivial: just pop off the REPEAT block, and go to AS.NEWSTT to continue execution after the UNTIL statement. I could pop the block off with seven PLA's, but I used the technique of adding 7 to the stack pointer instead.

Naturally, this package was assembled to sit in page 3, along with 99 other machine language things you use. You can easily move it to another location, just by changing the origin (line 1180). Or you can use the routines with Amper-Magic or the Routine Machine. Note that the routines themselves are relocatable run-anywhere code (no data references, JSR's, or JMP's to points within the routines). You will have to shorten the routine names to four or less characters to use them with Amper-Magic.

Pascal has some other looping constructs which you might like to see in Applesoft. Now that you see how I did this one, why not try your hand at coding REPEAT WHILE?

 1000  *SAVE S.REPEAT/UNTIL
 1010  *--------------------------------
 1020  *   BY BOBBY DEEN
 1030  *      629 WINCHESTER DR
 1040  *      RICHARDSON,TX. 75080
 1050  *      (214) 235-4391
 1060  *--------------------------------
 1070  AMPERSAND.VECTOR .EQ $3F5
 1080  AS.FRMEVL  .EQ $DD7B     EVALUATE A FORMULA
 1090  AS.CHRGOT  .EQ $00B7     GET CHAR AT TXTPTR
 1100  AS.TXTPTR  .EQ $00B8     POINT TO PROGRAM TEXT
 1110  AS.SYNERR  .EQ $DEC9     SYNTAX ERROR
 1120  AS.ADDON   .EQ $D998     ADDS (Y) TO TXTPTR
 1130  AS.CURLIN  .EQ $75       CURRENT LINE NUMBER
 1140  FAC.EXP    .EQ $9D       EXPONENT OF FAC
 1150  AS.BADFOR  .EQ $DD0B     NEXT WITHOUT FOR ERROR
 1160  AS.NEWSTT  .EQ $D7D2     EXECUTE NEW STATEMENT
 1170  *--------------------------------
 1180         .OR $300
 1190         .TF B.REPEAT/UNTIL
 1200  *--------------------------------
 1210  START  LDA #AMPER.PARSE
 1220         STA AMPERSAND.VECTOR+1
 1230         LDA /AMPER.PARSE
 1240         STA AMPERSAND.VECTOR+2
 1250         RTS
 1260  *--------------------------------
 1270  AMPER.PARSE
 1280         LDX #-1      START OF TABLE
 1290  .1     LDY #-1      START OF AMPER-CALL
 1300  .2     INX
 1310         INY
 1320         LDA TABLE,X  NEXT CHAR FROM TABLE
 1330         BEQ .4       END OF KEYWORD, MATCHED
 1340         CMP (AS.TXTPTR),Y   COMPARE WITH AMPER-CALL
 1350         BEQ .2       MATCHES SO FAR
 1360  *---SKIP TO NEXT TABLE ENTRY-----
 1370  .3     INX          ...TO END OF KEYWORD
 1380         LDA TABLE,X
 1390         BNE .3
 1400         INX          ...OVER THE ADDRESS
 1410         INX
 1420         BNE .1       ...ALWAYS
 1430  *---MATCHED A KEYWORD------------
 1440  .4     JSR AS.ADDON      ADJUST TXTPTR PAST KEYWORD
 1450         LDA TABLE+2,X     GET ADDRESS AND BRANCH
 1460         PHA
 1470         LDA TABLE+1,X
 1480         PHA
 1490         JMP AS.CHRGOT     GET CHAR AT TXTPTR
 1500  *--------------------------------
 1510  TABLE
 1520         .HS 52455045C500    "REPEAT"
 1530         .DA REPEAT-1
 1540         .HS 554E54494C00    "UNTIL"
 1550         .DA UNTIL-1
 1560         .HS A15200          "POPR"
 1570         .DA POPR-1
 1580         .HS 00              ANYTHING
 1590         .DA AS.SYNERR-1
 1600  *--------------------------------
 1610  * REPEAT COMMAND
 1620  *--------------------------------
 1630  REPEAT
 1640         BNE SYNERR       NOT THERE
 1650         PLA          SAVE RETURN ADDRESS
 1660         TAX
 1670         PLA
 1680         TAY
 1690         LDA AS.CURLIN+1 PUSH CURRENT LINE NUMBER
 1700         PHA
 1710         LDA AS.CURLIN
 1720         PHA
 1730         LDA AS.TXTPTR+1 PUSH TEXT POINTER
 1740         PHA
 1750         LDA AS.TXTPTR
 1760         PHA
 1770         LDA #$B8 IDENTIFIER FOR REPEAT LOOP
 1780         PHA      SO THIS ISN'T MISTAKEN FOR FOR/NEXT
 1790  *               OR GOSUB/RETURN
 1800         TYA       PUT RETURN ADDRESS ON STACK
 1810         PHA
 1820         TXA
 1830         PHA
 1840         RTS       AND GO BACK
 1850  *--------------------------------
 1860  * PROCESS UNTIL COMMAND
 1870  *--------------------------------
 1880  UNTIL
 1890         JSR AS.FRMEVL GET EXPRESSION
 1900         LDA FAC.EXP  GET EXPONENT
 1910         BNE POP.IT   TRUE,END LOOP
 1920         TSX          KEEP LOOPING
 1930         LDA $103,X
 1940         CMP #$B8     IS IT A REPEAT?
 1950         BNE BADFOR   NO,ERROR
 1960         LDA $104,X   GET THE DATA
 1970         STA AS.TXTPTR AND TELL APPLESOFT
 1980         LDA $105,X
 1990         STA AS.TXTPTR+1
 2000         LDA $106,X
 2010         STA AS.CURLIN
 2020         LDA $107,X
 2030         STA AS.CURLIN+1
 2040         INX          WE DON'T NEED THE RETURN ADDRESS
 2050         INX
 2060         TXS          KILL SUB CALL
 2070         JMP AS.NEWSTT  NEW STATEMENT
 2080  *--------------------------------
 2090  * POP A REPEAT LOOP OFF STACK
 2100  *--------------------------------
 2110  POPR
 2120         BNE SYNERR
 2130  POP.IT TSX          EXP TRUE,SO END LOOP
 2140         LDA $103,X   MAKE SURE IT IS A REPEAT
 2150         CMP #$B8
 2160         BNE BADFOR
 2170         TXA
 2180         CLC
 2190         ADC #7       PULL 7 THINGS
 2200         TAX
 2210         TXS
 2220         JMP AS.NEWSTT
 2230  *--------------------------------
 2240  BADFOR JMP AS.BADFOR
 2250  SYNERR JMP AS.SYNERR
 2260  *--------------------------------

Advertising in AAL

Once again, the price per page of advertising in Apple Assembly Line is going up. The December issue will run $90 for a full page, $50 for a half page.


Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 280300, Dallas, TX 75228. Phone (214) 324-2050 Subscription rate is $15 per year, in the USA, sent Second Class Mail; $18 per year sent First Class Mail in USA, Canada, and Mexico; $28 per year sent Air Mail to 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.)