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%!
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.
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 |
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.
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 |
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.
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.
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 / |
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.)