SpEEdy TEXT BUffER DisplAy
by THOMAS J. ANdREWS, CONTRibUTiNG AUTHOR
A while ago I was writing a program that read text files byte by byte, manipulated the text, and output into a new file. To be of much use, the program had to handle files of any length. Long files would take a long time to process, even using compiled Turbo-BASIC and a RAMdisk. My biggest problem was how to keep the user informed the program was progressing properly without extending processing time to do it.
The obvious solution was to put some sort of continually updated progress message on the screen. A counter is what came to mind first. Because I was handling the files in a single byte fashion, a byte counter was the easiest to use. Essentially, all I had to do was to add the following line every time I did a GET from the disk file:
BYTES=BYTES+1:POSITION X,Y:? BYTES
Counter Adds Time
Simple, right? Yes indeed-and time consuming. That one little line added nearly 20% to my processing time! Here's why: first, both Atari BASIC and Turbo-BASIC can only do floating point arithmetic. Incrementing a simple counter like this takes several machine cycles. As for the rest, these two statements invoke the Editor handler and the CIO routines, involving several more cycles each.
There's a better way-but it takes a bit more programming savvy. The ANTIC chip interrupts the CPU every 1/60th of a second to redraw the screen display. It does this through the use of the Display List, which is just a series of instructions telling which screen mode to use for each line and where in RAM to find the information to be displayed. The ANTIC chip is oblivious to the contents of this screen memory and just displays whatever happens to be in the RAM where it is directed. If the contents of that memory are changed, the display changes automatically.
A new Display List is created every time you enter a new GRAPHICS mode. But you aren't locked into using this Display List as it is; it can be customized quite easily if you pay attention to what you're doing. One of the easiest things to do is change the location of the screen memory. Let's say you have a fairly active text buffer in your program: one that you fill, empty, and refill regularly. If you were to redirect the ANTIC chip to display the contents of this buffer by changing the screen memory address in the Display List, you would display those contents as they change, with no program delay at all.
DISPCOPY: A Turbo-BASIC Demo
It's not quite that simple, of course. There are several things you have to set up and a few you have to watch out for. My demonstration program, DISPCOPY, shows how you can do it. This program does perform a useful function: it copies the contents of one file into another. This isn't the fastest way to do things, but it's a good way to demo my display technique. It's written in Turbo-BASIC because there are some handy commands in that language that make it much easier to write this program.
By the way, if you don't have Turbo-BASIC, get it. It's three times faster and completely compatible with Atari BASIC, and in the XL/XE machines has about 3900 more bytes of available memory. There's also a version available for the 400/800 models. It can be found at most PD software suppliers, and may be downloaded for free from most BBSes that support the Atari 8-bit.
When a character is displayed on the screen, the screen handler draws it according to a map stored in memory. There are 128 of these maps, collectively called the character set. Characters with values of over 127 use the same maps, just reversing the colors. Information is sent to most I/O devices in ATASCII code. For example, an "A" is equal to a value of 65. Unfortunately, the character maps are NOT stored in ATASCII order, but in a different one called Screen Code. There, an "A" is given a value of 33.
When you PRINT a character to the screen, the ATASCII value is converted to screen code and placed in the appropriate spot in screen memory. My method bypasses the conversion and runs the data directly through the screen memory block. If I want the display to show the correct ATASCII characters, I must create a new character set in ATASCII order.
The first step is to reserve a "safe" area of RAM for the new set, a place where BASIC won't alter it. This I do by lowering the contents of location 106, MEMTOP. This is the upper limit of available memory. I'll need 1024 bytes of space, or 4 pages. Also, a character set must begin on an even-numbered page boundary.
400/800 machines present a problem that XL/XE's don't have. In that operating system, a Clear Screen or GRAPHICS command will not only clear the screen memory, which usually resides just below the top of memory, but will also clear out 800 bytes of memory above the screen memory. Thus, in those machines, the first 800 bytes above MEMTOP are not safe. 400/800 owners will want to use POKE 106,NTOP-4 in line 130 of the program to reserve enough extra space to compensate.
The next step is to load the custom character set. I'll be using the internal set, so all I have to do is copy it from the OS ROM, reordering it as I do so. I use Turbo-BASIC's MOVE command, which works at machine language speed, much faster than an Atari BASIC FOR-NEXT loop. Any GRAPHICS 0 character set could be used, as long as it's loaded in ATASCII order.
Memory location 756 contains the page number of the start of the current character set, and points to the ROM set at boot-up. All you need to do to use another set-once it's loaded-is POKE the page number of the start of that set. Remember, this must be an even number to function properly.
Now it's time to DIMension the string buffers I'll be using. There are only three buffers used in DISPCOPY: the source file name, the destination file name, and the display. The file name buffers can be put anywhere and are DIMensioned right away. The display buffer, however, has some restrictions that require cautious handling.
First, because of the structure of the ANTIC chip, the screen memory cannot cross a 4K boundary without a special Display List instruction. A GRAPHICS 0 screen uses 960 bytes. If I were to just DIM the buffer, it would be placed directly after FOUT$, the destination file name buffer. This could cross a 4K boundary, and in fact does just that. So, what I do is make the computer check to see if it would cross a boundary if it is put there. This is done by adding the size of FOUT$ to ADR(FOUT$) to get the potential address of the display buffer. If the display buffer would cross a 4K boundary, all I have to do is DIM a dummy string that's long enough to put the display buffer over that boundary.
XL's with RAMBO memory expansions are another consideration. These machines access their expanded memories by switching different 16K banks into the area from locations 16384 through 32767 ($4000 to $7FFF). If the display buffer were to fall within this area, and you were to use your RAMdisk while this display was in effect, you would get momentary flashes on the screen as the different banks are accessed. This doesn't affect the operation of the program but can be very annoying, it's best to avoid this area of memory for display purposes if possible. My demonstration program checks for this and compensates if necessary using the same method described previously.
Unfortunately, if your own program is structured so the display buffer just barely crosses into the RAMBO area, you waste 16K with this check. If you use this technique and start getting ERROR 2's (Insufficient Memory), try DIMensioning other strings and arrays before your display buffer. You'll still need the checks, and you'll need the last variable that is DIMmed to be a string so you can use the ADR function to determine the prospective address of the display buffer. You can DIM a dummy string only one byte long for this purpose.
It's quite possible to write a program that just happens to avoid these pitfalls, in which case you might consider leaving out the checks. Don't! The string buffers are always placed at the end of the program area. As you expand and develop the program, these buffers will change location and could eventually violate one of the restrictions. When that happens, you'll spend agonizing hours trying to find out what happened to the part of your program that was working fine before.
After checking these things out and DIMensioning the dummy string (if needed), the display buffer is DIMensioned. Then, the program gets the devices and file names for the operation. DISPCOPY won't copy to or from the S: or E: devices but can use any other device. For example, you could copy a file directly from a disk to a printer. Because there's no provision for disk swapping, you won't be allowed to use the same device and filename for both source and destination. You may use the same device if different filenames are used.
Down To Work
Once the files have been opened, it's time to switch to the working display. Location 559 is POKEd with a 0 to turn off the display. As the vari ous parameters are changed, some extraneous garbage will flash on the screen, and this POKE will hide it from view. Location 756 is POKEd with NTOP, to switch to the reordered character set.
The Display List is next. Locations 560 and 561 contain the address of the first byte of the Display List. The fifth and sixth bytes of the standard GRAPHICS 0 Display List contain the address of the beginning of screen memory. After storing the original address so it can be restored later, the address of the working display buffer is DPOKEd into its place in the list. Finally, the screen is turned back on with a POKE 559,34.
Now the program can go on pretty much as it normally would. In DISPCOPY, I just load up the buffer from the source file with a BGET command and empty it into the destination file with the BPUT command. This is done repeatedly until an error occurs. If it's an end-of-file error, I find out how many bytes were transferred on the last command, send them on to the destination file, and close both files.
There are some restrictions in effect when using this technique. It's mainly designed for those times when the computer is expected to spend long periods of time doing its own thing, with no user interaction. As far as everything but the Display List is concerned, the program is displaying the normal GRAPHICS 0 screen. If an untrapped error occurs, the program will stop, but the display will remain as is. The error message won't be reported.
You may PRINT to the screen if you like, but the handler will put the information into the normal screen memory so it won't be displayed until the Display List is directed back there again. Also, a GRAPHICS command will write a new Display List, so it can't be used unless you want to leave the custom display. To return to the normal display, you must POKE the page number for the standard character set back into location 756 and restore the screen memory address to the Display List. It is again best to turn off the display while you do this, which can be accomplished in two ways. You might DPOKE the original address back into the Display List, or you could use a GRAPHICS command to clear the screen and write a new Display List all in one operation. I use both methods in the program, one at the normal end of the operation and the other in the error trap.
That's all there is to it, except for one last detail. Since DISPCOPY automatically lowers MEMTOP every time it runs, the contents of that location should be restored at every possible exit of the program. If this weren't done, repeated runs of the program would eventually reduce the available memory to the point where you run out. This is why the BREAK key is disabled, so the program won't be unexpectedly interrupted. BREAK is restored to service just before the final exit.
My buffer display technique works just as well when compiled with the Turbo-BASIC compiler as it does with the interpreter, so you can run DISPCOPY as a compiled program if you wish. You won't see much speed difference with this particular program since it already uses the faster commands of Turbo-BASIC for operations that would be slow with Atari BASIC. With other programs, though, there could be a big difference.
DISPCOPY can be used to visually demonstrate differences in speed of several programming techniques. Those of you with RAMdisk capability should try copying a file first with a drive and then with the RAMdisk. The visual representation is dramatic. Another exercise is to make copies with the write verify function in and out of operation. POKE 1913,80 to turn off write verify and POKE 1913,87 to turn it back on. (This is guaranteed to work ONLY with DOS 2.0 or DOS 2.5.) You can also observe the difference in speed between copying a file on the same disk versus using two drives. The difference is due to the amount of head shuttling from file to file in a one-drive operation.
One last thing is to try substituting the following lines for those in the program:
460 FOR LN=1 TO 960:GET #1,A:DISP$(LN,LN)=CHR$(A): NEXT LN
470 PRINT #2;DISP$;
510 IF LN>1 THEN PRINT #2;DISP$(1,LN-1);
These are the statements that would have been used in the copying loop if this program were written in Atari BASIC. You'll soon see why I chose Turbo-BASIC's BGET command!
If you'd like to see the program for which I developed this technique, you'll find it in GEnie's 8-bit RT Library as file #6059. It's a text reformatter (called REFORMAT 2.01) that can change the record size of almost any text file. Download it and try it out. The display technique I've described in this article can have many applications; I've used only one here. I leave the others for you to discover for yourself. Enjoy!
Tom Andrews is the 8-bit librarian of the Syracuse ACE usergroup and an honorary member of the OI'Hacker's Atari User Group.
[Editor's Note: The latest version of Tom's REFORMAT program is included on AC's Feb. 1993 Software Disk, along with the Turbo-BASIC tokenized (DISPCOPY.TUR) and LISTed versions (DISPCOPY.LIS) of the utility described in this article.]