Volume 3 -- Issue 2 | November 1982

In This Issue...

- A Sight of Sound
- Your Apple Can Talk
- Speaking of Speech
- Even Faster Primes
- Moving the Symbol Table
- EXEC Without END from Applesoft
- Applesoft Program Locator
- REPEAT and UNTIL for Applesoft

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:

- I sped up the zero-memory loop by putting more STA's within the loop.
- I removed the CLC from the main loop. After all, why CLC within the loop if you're looping on a BCC condition?
- I removed the LDA #$FF from the main loop. It was there to be sure a non-zero value gets stored in non-prime slots, but why LDA #$FF if the accumulator never contains $00 within the loop?
- I changed the way squares of primes are computed. Charlie did it using a quick 8-bit by 8-bit multiply. I took advantage of a little number theory, and shaved off some time.

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.)