More BASIC word wrap versions

Welcome to another exciting installment of text word wrapping in Color BASIC! This time, I will share another word wrap submission, then compare all four versions in speed and code size.

Be sure to check out part 1, part 2, and part 3.

I originally shared some BASIC code I was writing to do word wrapping of text. My program needed to run on 32, 40 or 80 columns on the CoCo, and I did not want to hard code versions of every screen for each screen width.

CoCo/MC-10 BASIC wiz Jim Gerrie passed along his three-line version, and now CoCoSDC designer Darren Atkinson shares some more tweaks. He writes:

Hi Allen,

Here is a submission for your Word Wrap article. It’s not my own design. I just took the liberty of making a couple changes to Jim Gerrie’s code:

1. It will now use the last column in a line.
2. It runs a bit faster.

By focusing on speed, the trade-off is somewhat larger code size and the allocation of a few more variables.

– Darren

His update looks like this:

0 CLEAR200:DIMCC,VP,C1,LN,SP:GOTO10
1 C1=1:CC=WD+2:VP=VARPTR(M$):VP=PEEK(VP+2)*256+PEEK(VP+3)-1:LN=LEN(M$)-1:SP=32
2 CC=CC-1:IFCC<LN ANDPEEK(VP+CC)<>SP ANDCC>C1 THEN2ELSEC2=CC-C1:IFCC=C1 THENC2=WD:CC=C1+WD-1
3 PRINTMID$(M$,C1,C2);:C1=CC+1:CC=C1+WD:IFC2<>WD ORLN+1<WD THENPRINT
4 IFC1<LN THEN2ELSERETURN
10 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 M$="This should not need to wrap.":GOSUB 1
90 PRINT "LONG STRING:"
100 M$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
110 PRINT "WORD > WIDTH:"
120 M$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
130 PRINT"TIME TAKEN:"TIMER-TM
140 END
Word-wrap by Darren Atkinson.
Word-wrap by Darren Atkinson.

Hi version is four lines of actual code (1-4) and uses a few more variables. He is using a technique I had not seen before. Rather than check characters using MID$(), he gets the address of the string using VARPTR() and then PEEKs memory. I will have to benchmark this and see the time differences. His version clocks in with a count of 192.

Let’s see how all four versions stack up, from fastest to slowest.

  1. My version 2 (LEFT$/RIGHT$): 107
  2. My version 1 (MID$): 114
  3. Darren Atkinson’s four line version: 192
  4. Jim Gerrie’s three line 3rd version: 248

That takes care of speed, but what about memory usage? In order to do a true apples-to-apples comparison, I need to alter my versions so they use the same starting line number at Jim’s and Darren’s. Jim moves subroutines to the start of the program so they are found quicker. He also numbered by 1 as a space-spacing step (GOSUB1 takes three bytes less than GOSUB1000). Since my versions are too long to fit before the start of the test code at line 10, I am going to make the test code start at 100, and GOSUB to line 1 for the word wrap routine. The new test program will look like this:

100 CLS
110 INPUT"SCREEN WIDTH [32]";WD
120 IF WD=0 THEN WD=32
130 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
140 TIMER=0:TM=TIMER
150 PRINT "SHORT STRING:"
160 A$="THIS SHOULD NOT NEED TO WRAP.":GOSUB 1
170 PRINT "LONG STRING:"
180 A$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
190 PRINT "WORD > WIDTH:"
200 A$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
210 PRINT"TIME TAKEN:"TIMER-TM
220 END

Line 0 will have any needed CLEAR command (for extra string space), and could use DIM to pre-declare any variables used later. Then it should GOTO 100 to start the test routine. The word wrap routine will start at line 1.

Jim provided me with an existing routine he had, so he altered the test program to use M$ for the message to wrap. In order to keep all cases the same, I have changed Jim’s (and Darren’s) test programs to use A$. Now all four word wrap routines will be as similar as possible.

I also removed the DIMs for now, since I wanted everything in the .BAS file to be the same except for the word wrap routine.

My version 1 (LEFT$/RIGHT$):

1 REM WORD WRAP V1
2 '
3 'IN : A$=MESSAGE
4 ' UC=1 UPPERCASE
5 ' WD=SCREEN WIDTH
6 'OUT: LN=LINES PRINTED
7 'MOD: ZS, ZE, ZC
8 '
9 LN=1
10 IF A$="" THEN PRINT:RETURN ELSE ZS=1
11 IF UC>0 THEN FOR ZC=1 TO LEN(A$):ZC=ASC(MID$(A$,ZC,1)):IF ZC<96 THEN NEXT ELSE MID$(A$,ZC,1)=CHR$(ZC-32):NEXT
12 ZE=LEN(A$)
13 IF ZE-ZS+1<=WD THEN PRINT MID$(A$,ZS,ZE-ZS+1);:IF ZE-ZS+1<WD THEN PRINT:RETURN
14 FOR ZE=ZS+WD TO ZS STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZC=0 ELSE ZE=ZE-1:ZC=1
15 IF ZE<ZS THEN ZE=ZS+WD-1
16 PRINT MID$(A$,ZS,ZE-ZS+1);
17 IF ZE-ZS+1<WD THEN PRINT
18 LN=LN+1
19 ZS=ZE+1+ZC
20 GOTO 12

My version 2 (MID$):

1 REM WORD WRAP V2
2 '
3 'IN : A$=MESSAGE
4 ' UC=1 UPPERCASE
5 ' WD=SCREEN WIDTH
6 'OUT: LN=LINES PRINTED
7 'MOD: ZC, ZE, ZS
8 '
9 LN=1
10 IF A$="" THEN PRINT:RETURN
11 IF UC>0 THEN FOR ZS=1 TO LEN(A$):ZC=ASC(MID$(A$,ZS,1)):IF ZC<96 THEN NEXT ELSE MID$(A$,ZS,1)=CHR$(ZC-32):NEXT
12 ZE=LEN(A$)
13 IF ZE<=WD THEN PRINT A$;:IF ZE<WD THEN PRINT:RETURN
14 FOR ZE=WD+1 TO 1 STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZP=0 ELSE ZE=ZE-1:ZP=1
15 IF ZE=0 THEN ZE=WD
16 PRINT LEFT$(A$,ZE);
17 IF ZE<WD THEN PRINT
18 LN=LN+1
19 A$=RIGHT$(A$,LEN(A$)-ZE-ZP)
20 GOTO 12

Jim Gerrie’s three line version:

1 C1=1:CC=WD+1
2 CC=CC-1:ON-(MID$(A$,CC,1)<>""ANDMID$(A$,CC,1)<>" "ANDCC>C1)GOTO2:C2=CC-C1:IFCC=C1 THENC2=31:CC=C1+WD-2
3 PRINTMID$(A$,C1,C2):C1=CC+1:CC=C1+WD:ON-(C1<=LEN(A$))GOTO2:RETURN

Darren Atkinson’s four line version:

1 C1=1:CC=WD+2:VP=VARPTR(A$):VP=PEEK(VP+2)*256+PEEK(VP+3)-1:LN=LEN(A$)-1:SP=32
2 CC=CC-1:IFCC<LN ANDPEEK(VP+CC)<>SP ANDCC>C1 THEN2ELSEC2=CC-C1:IFCC=C1 THENC2=WD:CC=C1+WD-1
3 PRINTMID$(A$,C1,C2);:C1=CC+1:CC=C1+WD:IFC2<>WD ORLN+1<WD THENPRINT
4 IFC1<LN THEN2ELSERETURN

To determine program size, I will PRINT MEM before loading,and then load the full test program and delete line 0 (the CLEAR/GOTO) and anything past 100 (the test program). I will print MEM again and subtract.

Here they are again, from smallest code size to largest:

  1. Jim Gerrie’s three line version: 176 bytes
  2. Darren Atkinson’s four line version:  236 bytes
  3. My version 2 (LEFT$/RIGHT$): 500 bytes
  4. My version 1 (MID$): 542 bytes

Now the order is nearly reversed, with Jim’s and Darren’s versions substantially smaller than my versions. But, my version has those big REM statements and plenty of spaces and lines that could be combined. Mine also returns the number of lines printed (LN) and has the code for uppercase conversion and a check at the start to make sure we aren’t passed an empty string. If an empty string is passed to Jim’s, it gives “?FC ERROR IN 2”. Darren’s just returns. I will have to inspect his code and see if that was intentional. I should probably add an empty string to the test case.

I will remove the uppercase (UC) and line count (LN) code from mine, and try to pack things together. I want to keep the empty string check since error checking is good and since it seems Darren’s does this.

My optimized version 1 (MID$) now looks like this:

1 IFA$=""THENPRINT:RETURNELSEZS=1
2 ZE=LEN(A$):IFZE-ZS+1<=WD THENPRINTMID$(A$,ZS,ZE-ZS+1);:IFZE-ZS+1<WD THENPRINT:RETURN
3 FORZE=ZS+WD TOZS STEP-1:IFMID$(A$,ZE,1)<>" "THENNEXT:ZC=0ELSEZE=ZE-1:ZC=1
4 IFZE<ZS THENZE=ZS+WD-1
5 PRINTMID$(A$,ZS,ZE-ZS+1);:IFZE-ZS+1<WD THENPRINT
6 ZS=ZE+1+ZC:GOTO2

My optimized version 2 (LEFT$/RIGHT$) now looks like this:

1 IFA$=""THENPRINT:RETURN
2 ZE=LEN(A$):IFZE<=WD THENPRINTA$;:IFZE<WD THENPRINT:RETURN
3 FORZE=WD+1TO1STEP-1:IFMID$(A$,ZE,1)<>" "THENNEXT:ZP=0ELSEZE=ZE-1:ZP=1
4 IFZE=0THENZE=WD
5 PRINTLEFT$(A$,ZE);:IF ZE<WD THENPRINT
6 A$=RIGHT$(A$,LEN(A$)-ZE-ZP):GOTO2

Now let’s see how things stack up.

  • My version 1 (MID$): size 240 bytes / speed 107
  • My version 2 (LEFT$/RIGHT$): size 199 bytes / speed 99

Wow. By removing extra functionality, REMs, removing spaces, and packing lines together, I went from 542 to 240 for version 1, and from 500 to 199 in the second version. For speed, version went from 114 to 107, and version 2 went from 107 to 99.

Out of string space!
Out of string space!

…but I should point out that, with the default 200 bytes reserved to variables in BASIC, my second version crashed with an “?OS ERROR” (out of string space). I had to CLEAR255 to make it work. The actual number has to be large enough to hold the biggest string being passed in, plus the overhead for the string manipulation using LEFT$/RIGHT$. To really know how much memory a BASIC program takes up, we really do need to consider its variable usage.

And now the new rankings for code size (smallest to largest):

  1. Jim Gerrie’s three line 3rd version: 176 bytes
  2. My version 2 (LEFT$/RIGHT$): 199* bytes (see note below)
  3. Darren Atkinson’s four line version: 236 bytes
  4. My version 1 (MID$): 240 bytes

…and for speed:

  1. My version 2 (LEFT$/RIGHT$): 99 (was 107)
  2. My version 1 (MID$): 107 (was 114)
  3. Darren Atkinson’s four line version: 192
  4. Jim Gerrie’s three line 3rd version: 248

NOTE: Since my version 2 required me to do a CLEAR255 for it to run, that means it actually took up 254 bytes (code, plus the extra 55 bytes allocated past the default 200 for variables).

It is tricky to calculate variable usage since the sizes of strings passed in will be a factor, and maybe it would have ran with CLEAR 210 or CLEAR 220 to save a bit more. To crash-proof a program, you need to ensure CLEAR is done to cover the maximum size of string usage in the worst case. Here, we’re not that concerned.

In conclusion, right now if ever byte of program space matters (such as Jim’s case when programming in 4K), his is best. For overall speed, my version 2 is best (but it uses the most memory between code and variable storage). Even my version 1 (with no extra string space required is still faster than the next fastest.

Can we do better? Can you do better? Let’s find out.

I will make a .DSK image (that works in the XRoar emulator, and should be loadable via CoCoSDC on a real CoCo) as soon as I figure out how to post one in WordPress.

To be continued… Probably…

Word wrap revisited…

  1. 2015/1/5 Update: Jim’s v3 code has been fixed.

In the “CoCo Community”, Jim Gerrie is well-known for his prolific BASIC programs. He and his son have cranked out an endless assortment of games over the years for both the Radio Shack Color Computer and the MC-10. Since the MC-10 also started out as a 4K computer, he has long been since writing programs to fit in such little memory. He is the first one to submit programs to the 1980 4K CoCo Programming Challenge I am hosting.

After he saw my word wrap article and its follow-up with updated code, he was kind enough to share a simple word wrap routine that he uses. His looks like this:

...
1 C1=1:CC=WD
2 FORCC=CC TOC1+2STEP-1:IFMID$(M$,CC,1)<>""ANDMID$(M$,CC,1)<>" "THENNEXT
3 PRINTMID$(M$,C1,CC-C1):C1=CC+1:CC=C1+(WD-1):IFC1<=LEN(M$)THEN2
4 RETURN
...

FOUR lines compressed of code. Since he works with low-memory BASIC so often, he demonstrates plenty of the optimizing techniques I mentioned earlier and some I didn’t mention. Not only does he number his program by 1s, he also places the subroutine at the start of the program. Every time you GOTO or GOSUB, Color BASIC will either search forward (if the line number you are going to is higher than the one you are currently on), or start at the TOP of your program and search forward. This means if you had something like this:

100 REM A REALLY BIG PROGRAM
...
150 GOSUB 1000
...
995 END
1000 REM MY SUBROUTINE
1010 PRINT "HELLO!"
1020 RETURN

…when you called “GOSUB 1000” from line 150, BASIC would then have to scan forward until it finds line 1000. If you have hundreds of lines of BASIC code, this is a big waste of CPU time.

However, if you placed that routine at the start of the program (and had a line at the beginning to jump past it), every time you called the routine it would find it much quicker:

19 GOTO 100
20 REM MY SUBROUTINE
30 PRINT "HELLO!"
40 RETURN
100 REM A REALLY BIG PROGRAM
...
150 GOSUB 20
...
995 END

Keep in mind, there are always tradeoffs. GOTO works the same way, and ant complex BASIC program is usually full of GOTOs. Every GOTO has to do the same scanning (forward, or starting at the top) to find the routine. This means each GOTO to an earlier line number now has to scan past all your subroutine lines every time you GOTO. For the fastest code, you need to take in to consideration what happens more often — GOSUBing to functions, or GOTOs to higher line numbers.

As to how significant the time savings can be, I plan to write another article in the near future with some simple benchmarks.

Here is Jim’s complete program, which uses my WRAPTEST code to process the same strings:

0 CLEAR200:DIMC1,CC,M$:GOTO10
1 C1=1:CC=WD
2 FORCC=CC TOC1+2STEP-1:IFMID$(M$,CC,1)<>""ANDMID$(M$,CC,1)<>" "THENNEXT
3 PRINTMID$(M$,C1,CC-C1):C1=CC+1:CC=C1+(WD-1):IFC1<=LEN(M$)THEN2
4 RETURN
10 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 M$="This should not need to wrap.":GOSUB 1
90 PRINT "LONG STRING:"
100 M$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
110 PRINT "WORD > WIDTH:"
120 M$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
130 PRINT"TIME TAKEN:"TIMER-TM
140 END
Jim Gerrie's CoCo tiny and fast word-wrap routine.
Jim Gerrie’s tiny and fast word-wrap routine for CoCo BASIC.

His routine is small and fast. It does not implement UC uppercase conversion, does not use the last character of the line, and has issues with words longer than the line length — but just look at the code size and speed savings! This is a great, efficient approach when you can control the output. As long as you aren’t displaying “supercalifragilisticexpialidocious” it works great.

However, Jim seems up to the challenge, and he made some small changes to allow his wrap routine to handle words longer than the screen width. Again, just four lines of code:

0 CLS:CLEAR255:DIMCC,C1,C2,M$:GOTO10
1 C1=1:CC=WD+1
2 CC=CC-1:ON-(MID$(M$,CC,1)<>""ANDMID$(M$,CC,1)<>" "ANDCC>C1)GOTO2:C2=CC-C1:IFCC=C1 THENC2=31:CC=C1+WD-2
3 PRINTMID$(M$,C1,C2):C1=CC+1:CC=C1+WD:ON-(C1<=LEN(M$))GOTO2:RETURN
10 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 M$="This should not need to wrap.":GOSUB 1
90 PRINT "LONG STRING:"
100 M$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
110 PRINT "WORD > WIDTH:"
120 M$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER "
121 M$=M$+"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
130 PRINT"TIME TAKEN:"TIMER-TM
140 END
Jim Gerrie's third version.
Jim Gerrie’s (v3) tiny and fast word-wrap routine for CoCo BASIC.

His new version, while still not using the last character of a line, now will break up words like supercalifragilisticexpialidocious. Because, you know, you have words like that in your text adventures all the time. And speaking of text adventures, he sent in this link to an archive of text adventures including many Jim (and his son) wrote. For many years he was using his original word wrap routine just fine (since he controlled the output) so all his additions are really just un-needed hoops to jump through for my abusing text case :)

Thanks, Jim, for sharing this with us! If anyone else wants to take a shot at it, feel free to send me a .DSK or .CAS image of your version. I have been using the XRoar emulator to do this testing (and take screen shots) on my Mac, and XRoar is also available for Windows, Linux and other systems.

Until next time, I hope things will be…

OK

…with you!

Optimizing Color BASIC – part 1

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7, part 8 and part 9.

A recent post by Art “ADOS” Flexser on the CoCo mailing list mentioned a few commercial programs for the Radio Shack Color Computer that were designed to optimize BASIC programs:

On Jan 2, 2015, at 12:26 AM, Arthur Flexser wrote:

A little googling identified the utility I was referring to as “The Stripper” from Eigen Systems. Apparently, there was also a similar program by Bob van der Poel called “Packer”. Maybe one or the other is available online someplace.

These should be useful for those finding tight memory for Basic programs in
the 4K contest.

The 4K contest he mentions is a programming challenge I recently organized to see what someone could do if they had the original 1980 4K Color Computer. You can read about it here:

http://www.cocopedia.com/wiki/index.php/1980_4K_CoCo_Programming_Challenge

In addition to the programs Art mentioned, I also had one given to me by Carl England. I used it on one of my programs at Sub-Etha Software. I do not know how it compares with The Stripper or Packer, but beyond true optimization of the code, it did about all you could do. Here are the tricks he used which produce the smallest and fastest version of a BASIC program, but they usually rendered it un-editable and hard to read.

NOTE: Code can be written for size or speed. Carl’s program optimized for size which in some cases made it faster since BASIC had less to parse, but as you will see, some optimizations might increase execution time making it slower. Maybe.

Remove all spaces

The less bytes to parse, the smaller and faster it will run. It has to be smart enough to know when spaces are required, such as “FORA=1TOQ STEP1” when a variable needs a space after it before another keyword.

Pack/combine lines where possible.

Any line called by a GOTO had to remain at the start, by following lines would be combined up to the max size of a BASIC line. Only when it had to be broken by logic (ELSE, etc.) would it not be combined. For example:

10 FOR I=1 TO 100
20 GOSUB 50
30 NEXT I
40 END
50 REM PRINT SOMETHING
60 PRINT "HELLO WORLD!"
70 RETURN

…would end up as:

10 FORI=1TO100:GOSUB60:NEXTI:END
60 PRINT"HELLO WORLD!":RETURN

Remove all REMs.

Obvious. I *think* his program would adjust a GOTO/GOSUB that went to a REM line to go to the next line after it, and remove the REM:

10 GOSUB 1000
20 END
1000 REM PRINT SOMETHING
1010 PRINT "HELLO"
1020 RETURN

…would become like:

10 GOSUB1010:END
1010 PRINT"HELLO":RETURN

NOTE: Even without packing, I learned not to GOTO or GOSUB to a REM since it required the interpreter to parse through the line. I would GO to the first line of code after the REM:

10 GOSUB 1010
...
1000 REM MY ROUTINE
1010 PRINT "HERE IS WHERE IT STARTS"
...

Every thing you do like that saves a bit of parsing time since it doesn’t have to start parsing 1000 and look up a token then realize it can ignore the rest of the line.

Not bad so far

But that’s not all!

The BASIC input buffer is some maximum number of bytes (250?). When you type a line to max, it stops you from typing more. When you hit ENTER, it is tokenized and could be much smaller. For instance, “PRINT” turns in to a one-byte token instead of the five characters. Carl’s program would combine and pack the tokens up to the max line size. Thus, it created lines that BASIC could run which were IMPOSSIBLE to enter on the keyboard. If I recall, you could LIST and they would print (?) but if you did an EDIT you were done.

Renumber by 1.

GOSUB2000 would take up one bye for the token, then four bytes for the line number. If you renumber by 1s, it might make that function be GOSUB582 and save a byte. Multiply that by every use of GOTO/GOSUB/ON GOTO/etc. and you save a bit.

Even without one of these compressors, try a before/after just doing a RENUM 1,1,1. I recently posted an article with a short (27 line) word wrap routine in BASIC. Doing this renum saved 7 bytes.

Removing NEXT variables.

I am not sure if this is just something I knew, or if his program also did it. If you use FOR/NEXT and it is used normally, you don’t need the NEXT variables like this:

10 FOR D=0 TO 3 'DRIVES
20 FOR T=0 TO 34 'TRACKS
30 FOR S=1 TO 18 'SECTORS
40 DSKI$ D,T,S,S1$,S2$
50 NEXT S
60 NEXT T
70 NEXT D

The NEXTs could also be

50 NEXT S,T,D

Or

50 NEXT:NEXT:NEXT

Ignoring spaces, “NEXT:NEXT:NEXT” takes up one less byte than “NEXTS,T,D” (NEXT is turned in to a token, so it is TOKEN COLON TOKEN COLON TOKEN versus TOKEN BYTEVAR COMMA BYTEVAR COMMA BYTEBAR”.

This takes up less memory, but there is a speed difference:

10 T=TIMER
20 FOR I=1 TO 10000
30 REM DO NOTHING
40 NEXT I
50 PRINT TIMER-T

Run this and it shows something in the 1181 range (speed varies based on other things BASIC does; pound on keys while it runs and it takes more time, for example). Change the “NEXT I” to “NEXT” and it shows something in the range of 1025. The more FOR variables, the more the savings, too.

10 TM=TIMER
20 FOR D=0 TO 3
30 FOR T=0 TO 34
40 FOR S=1 TO 18
50 REM DO SOMETHING
60 NEXT S
70 NEXT T
80 NEXT D
90 PRINT TIMER-TM

…shows around 345. Changing it to:

60 NEXTS,T,D

…changes it to around 336 – slightly smaller and faster since it is not parsing three different lines.

60 NEXT:NEXT:NEXT

…changes it to around 293! One byte smaller and slightly faster.

AND THERE’S MORE! But this post is already too long.

What I think would be cool is an actual BASIC OPTIMIZER that could do some smart things to programs, much like we do with the C language optimizers for asm and C. For example, noticing stupid things:

A$ = B$+"."+"."+C$

to

A$ = B$+".."+C$

And removing NEXT variables, or doing minor restructuring based on what it knows about the interpreter.

I suppose, back in the 80s, processors were so slow and memory so limited that doing such an optimizer might not be practical. But today, imagine how you could optimize a .BAS file on a modern computer (just like we have 6809 cross-hosted compilers that compile and link in the link of an eye).

Maybe this has already been done for some other flavor of BASIC? Hmmm. I may have to do some Googling.

A tale of two word wrap routines

  • 2015/1/4 Update: After reading this, you can check out the next installment, too.
  • 2015/1/5 Update: The BASIC listings should be fixed now.

In a recent article, I demonstrated a simple way to do a word wrap output routine in Extended Color BASIC on a Radio Shack Color Computer. When I went to try to use the routine, I found a few issues with it that I would like to address here.

My first version was able to word wrap all the way to the last character on a line, but I failed to test against a string that was longer than the screen width (with no spaces in it). This would only happen if you had a very long word on a very narrow screen (supercalifragilisticexpialidocious would be a word too long to fit on a 32-column CoCo screen, for example). I fixed this issue, and also optimized the routine to handle short strings better by checking for them at the start instead of the end.

I now have three test cases to check for: short strings that do not need to wrap, long strings that do need to wrap (with a word ending on the final 32nd column), and strings with word(s) longer than the screen width.

The first version I presented tried to avoid using any additional strings. Simply doing something like A$=A$+”BACON” causes a new larger string buffer to be allocated, the original data copied over, new data appended, and original string assigned to it and the old one released. I wanted to avoid that, so my routine worked just by printing out sections of the original string.

The end result was a routing that worked, but due to the nature of BASIC, was quite slow. Text did not just appear – it paused a bit before printing each line. In my test program, this was a bit annoying, so I decided to see if I could make it a bit faster.

While it’s true that the CPU overhead of doing string manipulation is significant, much more CPU time is spent processing tokenized BASIC. If you could save some BASIC time by doing string manipulation, you should come out ahead with a faster problem.

First, here is the test I am using for both my word wrap routines:

10 CLEAR200
20 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 A$="This should not need to wrap.":GOSUB 1000
90 PRINT "LONG STRING:"
100 A$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1000
110 PRINT "WORD > WIDTH:"
120 A$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1000
130 PRINT"TIME TAKEN:"TIMER-TM
140 END

And this is the updated original word wrap routine that does not allocate any extra strings. I have also renamed the variables in my subroutines, starting all the temporary variables with Z (ZS for start of string, ZE for end of string, etc.). I did this to avoid conflicts with my larger program.

1000 REM WORD WRAP V1
1010 '
1020 'IN : A$=MESSAGE
1030 ' UC=1 UPPERCASE
1040 ' WD=SCREEN WIDTH
1050 'OUT: LN=LINES PRINTED
1060 'MOD: ZS, ZE, ZP
1070 '
1080 LN=1
1090 IF A$="" THEN PRINT:RETURN ELSE ZS=1
1100 IF UC>0 THEN FOR ZP=1 TO LEN(A$):ZP=ASC(MID$(A$,ZP,1)):IF ZP<96 THEN NEXT ELSE MID$(A$,ZP,1)=CHR$(ZP-32):NEXT
1110 ZE=LEN(A$)
1120 IF ZE-ZS+1<=WD THEN PRINT MID$(A$,ZS,ZE-ZS+1);:IF ZE-ZS+1<WD THEN PRINT:RETURN
1130 FOR ZE=ZS+WD TO ZS STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZP=0 ELSE ZE=ZE-1:ZP=1
1140 IF ZE<ZS THEN ZE=ZS+WD-1
1150 PRINT MID$(A$,ZS,ZE-ZS+1);
1160 IF ZE-ZS+1<WD THEN PRINT
1170 LN=LN+1
1180 ZS=ZE+1+ZP
1190 GOTO 1110
CoCo_wordwrp1_mem
Size of BASIC word wrap program version 1.

When I start up a 32K (or larger) CoCo 1/2, PRINT MEM shows 22823 bytes available (some memory is reserved for graphics screens, so there are ways to get even more memory to BASIC if you aren’t doing graphics).

After loading the program, I check memory again and determined this version took 1101 bytes.

CoCo_wordwrp1_time
BASIC word wrap program version 1 output.

When I run the program, it handles all the test cases as I expected, and reports that the time taken as 113 (counts of the TIMER, with each number being based on the screen interrupt – 60hz on the US machines. Is it different on PAL machines that run at 50mhz?)

This gives me an idea of large the program is and how fast it is to run.

I then took this version and converted it (keeping the lines the same as much as possible) to one that used LEFT$() and RIGHT$() to chop the input string up as it went, making the code simpler at the cost of pushing more work on the BASIC string manipulation routines. It looked like this:

1000 REM WORD WRAP V2
1010 '
1020 'IN : A$=MESSAGE
1030 ' UC=1 UPPERCASE
1040 ' WD=SCREEN WIDTH
1050 'OUT: LN=LINES PRINTED
1060 'MOD: ST, EN
1070 '
1080 LN=1
1090 IF A$="" THEN PRINT:RETURN
1100 IF UC>0 THEN FOR ST=1 TO LEN(A$):EN=ASC(MID$(A$,ST,1)):IF EN<96 THEN NEXT ELSE MID$(A$,ST,1)=CHR$(EN-32):NEXT
1110 ZE=LEN(A$)
1120 IF ZE<=WD THEN PRINT A$;:IF ZE<WD THEN PRINT:RETURN
1130 FOR ZE=WD+1 TO 1 STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZP=0 ELSE ZE=ZE-1:ZP=1
1140 IF ZE=0 THEN ZE=WD
1150 PRINT LEFT$(A$,ZE);
1160 IF ZE<WD THEN PRINT
1170 LN=LN+1
1180 A$=RIGHT$(A$,LEN(A$)-ZE-ZP)
1190 GOTO 1110
CoCo_wordwrp2_mem
Size of BASIC word wrap program version 2.

When I load this version and check memory, it gives me 1058 — 43 bytes less than the first version. This also means there will be less bytes for the interpreter to have to process so it might run faster, too.

CoCo_wordwrp2_os
BASIC word wrap program version 2 out of string space error.

However, when I tried to run it, the program crashed with an Out of String Space error (?OS ERROR). Even though the BASIC program was smaller, it was now needing to allocate additional/temporary strings to do things like A$=LEFT$(A$,xxx). By default, Color BASIC sets aside 200 bytes for strings, and that was not enough for this version.

CoCo_wordwrp2_time
BASIC word wrap program version 2 output.

To fix this, I had to change the “CLEAR 200” on line 10 to be something larger, like “CLEAR 300”. That was enough for the program to run and I saw it’s time used was around 106. This was only slightly faster than the method that did not require extra string space so it really would only be useful if you needed that teeny tiny speed improvement and had the extra memory to spare.

Now, if the extra string space required was no more than the size saved by the smaller code, it would end up being an advantage. For my program, I do not expect memory to be an issue (it will be a fairly small program) so I may very well end up optimizing as many functions as I can for speed over memory.

Speaking of optimizations, coming up soon will be some notes on easy ways you can optimize BASIC to make your program take up less memory and/or run faster.

Until then, I’ll see you at the “OK” prompt…

CoCoSDC for TRS-80 Color Computer part 5

See also: part 1part 2part 3part 4, part 5 and part 6.

The story so far…

In 1980, Radio Shack introduced a computer with color. Later, they introduced a disk drive for it. Even later, everyone stopped using disk drives so an enterprising engineer created a disk drive replacement for a 34-year old computer. This is the story of that disk drive replacement.

Sort of.

Color BASIC PEEKs to get the stat and end of a BASIC program in memory.
Color BASIC PEEKs to get the stat and end of a BASIC program in memory.

As I write this, it has only been a week since I got out my old Tandy Color Computer 3 (CoCo). I have forgotten many things, and can’t remember why I remember other things. For instance, PEEK(25)*256+PEEK(26) will give you the memory location where your BASIC program starts, and PEEK(27)*256+PEEK(28) is the end of it. Subtract those two and you know the byte size of your program. But I digress.

I have now spent a bit of time playing with the CoCoSDC disk drive interface. Initially I just took all the CoCo .DSK image files I had downloaded in the past and copied them to an SD card and spent an evening loading up software. I even ran across a game written by former Sub-Etha Software guy, Joel Hegberg, that had been published in a 1989 issue of Rainbow magazine. Ah, great times. Such nostalgia.

On another evening, I dug through my storage room and finally located a few containers of old 5 1/4″ floppy disks. There had even been one left in my FD-501 floppy drive while it was stored for a decade. The disks I tested still seemed to read (though I already crashed one). I did some test backups, and marveled at how fast the CoCoSDC was compared to the mechanical floppy of decades past. I wrote a bit of BASIC code (similar to what I shared in part 4) to see if I could automate this process.

On my third evening of CoCo time, I started writing the actual backup program. When I was a beginner programmer, I used to blast through code and hack everything together until I had it working. Then I would make a printout, and begin completely rewriting it, all cleaned up and nicer. Think of it as manual two-pass optimization. :) This time, with two decades of programming experience more, I am trying to do more planning and designing.

In order to understand what makes this a challenge, one needs to understand the various types of disks that the CoCo could use over the years: from original single-sided 35-track RS-DOS disks, to double-sided 80-track disks for RS-DOS or alternate operating systems like OS-9. As long as the format used standard 256-byte sectors (i.e., no funky track layout formats which were common with copy protection methods), I wanted to be able to back it up to the CoCoSDC.

First, let’s discuss what it took to create and read these disks.

The Color Computer’s disk controller hardware has four drive select lines it can control. Actually, they are technically three drive selects and one side select. The original CoCo disk drives were single sided, and Radio Shack’s Disk BASIC used these four lines to select one of four possible single sided disk drives. The various disk related commands (BACKUP, DIR, COPY, etc.) allow drive numbers 0-3.

As drives went from 35-track to 40-track, and went from slow belt-driven drives to faster direct drives, CoCoists figured out how to patch Disk BASIC to access 40-tracks, and speed up the drives. (Disk BASIC accessed the drives with a 30ms step rate. Later drives could run at 6ms.)

Patching for 40-track drives was simple, but has some limitations. Disk BASIC reserved enough memory to maintain four 35-track Granual Allocation Tables (GATs, kind of like how FAT is the PC file system). When you patched it for 40-track drives, you could only use three (single sided) drives max. If you wanted to use an 80-track drive, you could only have one. There were alternate patch methods that allowed more drives, but they re-used memory which prevented you from having files open from different drives at the same time. Tradeoffs.

As double-sided drives became common, CoCoists realized that instead of using these four lines to select four single sided drives, they could be used to access three double-sided drives. Alternate operating systems like Flex and OS-9 were able to make easy use of double-sided drives, but Disk BASIC users had to come up with more patches.

As it turns out, patching for double-sided drives in Disk BASIC was easy if you just wanted to treat each side of the disk as it’s own single sided disk. If all you had was a single double-sided floppy drive (DRIVE 0), you could do a simple one-byte change that would make DRIVE 1 access the back side of DRIVE 0. Now you could double your storage without having to cut notches in the floppy disk and make them flippies you had to manually turn over.

If you had two physical double-sided drives, you could make them appear as four separate single-sided drives (DRIVE 0-3). This became a very common practice for many CoCoists, especially after Radio Shack released the FD-502 disk drive which was double-sided.

Now let me pass the buck… Here is an archived webpage that I found useful when explaining the drive selects and how to make Disk BASIC access the back side of double-sided drives:

http://www.oocities.org/theother_bob/coco_page/ccdisk00.htm

In it, he refers to the July 1985 issue of Rainbow magazine which had a very detailed article about Disk BASIC and drive selects and tracks and GATs and stuff. Here is a scan of that issue:

https://archive.org/details/rainbowmagazine-1985-07

(Wow. That was weird. I used Google to find that scan, and when I clicked the link, it opened right to the article I am talking about. Spooky. If yours does not, look on page 26, “Getting on the Right Track” by Colin J. Stearman. Wow again. I remember reading this when it first was published… 29 years ago!)

(Wow #2. I just realized this was the Anniversary issue where Rainbow included a reprint of their first 2-page newsletter. Look at the first letter to the editor on page 6 and see who wrote in to suggest that. “Great idea, Allen.” Ah, my lame claim to fame! Oh, and they printed my request for pen pals in the October 1985 issue on page 9 too. Such memories!)

But I digress…

In the Stearman article, he shows how to take a dual-drive system (with two double-sided drives) and make it look like four drives to Disk BASIC. DRIVE 0 would be the top side of the first drive, DRIVE 1 be the top side of the second drive, DRIVE 2 be the bottom side of the first drive, and DRIVE 3 be the bottom side of the second drive. (This kept DRIVE 0 and 1 as two different drives for compatibility.)

For Disk BASIC 1.1, here are POKEs. They work on a CoCo 3 because it’s Disk BASIC is copied in to RAM on startup and can be changed, but for earlier machines, it has to be a 64K machine with a “ROMRAM” program that copies the ROMs in to RAM then runs from RAM (so you can POKE the RAM copy and make changes). For systems where the ROM can’t be copied and ran out of RAM (16K or 32K), you would have to modify and burn a new ROM for the drive controller.

POKE 55453,1 'Drive 0 accesses top side of physical drive 0
POKE 55454,2 'Drive 1 accesses top side of virtual drive 1
POKE 55455,1+64 'Drive 2 accesses back side of physical drive 0
POKE 55456,2+64 'Drive 3 accesses back side of virtual drive 1

The drive select bits are bit 1 (first drive), bit 2 (second drive), bit 3 (third drive), and bit 7 (side select). If my understanding is correct, CoCoSDC hardware emulates bit 1, bit 2 and bit 7. In SDC-DOS, you can disable a drive so those bits control a real floppy controller if one is also plugged in via a Multi-Pak or similar device.

This means you might be able to do something like this:

DRIVE 0,"FLIPPY1.DSK" 'Make Drive 0 a .DSK image
DRIVE 1,"FLIPPY2.DSK" 'Make DRIVE 1 a .DSK image

POKE 55453,1 'Drive 0 top side of physical drive 0
POKE 55454,2 'Drive 1 top side of virtual drive 1
POKE 55455,1+64 'Drive 2 back side of physical drive 0
POKE 55456,2+64 'Drive 3 back side of virtual drive 1

DIR 0 'Uses first half of FLIPPY1.DSK
DIR 1 'Uses first half of FLIPPY2.DSK
DIR 2 'Uses second half of FLIPPY1.DSK
DIR 3 'Uses second half of FLIPPY2.DSK

But, you cannot. While the CoCoSDC hardware is able to emulate up to an 80 track double-sided drive, and while OS-9 can make use of this easily, the SDC-DOS software works differently and those original Disk BASIC patches will not work. The designer, Darren Atkinson, explained:

When the DSKCON routine in SDC-DOS is called to do I/O for a sector with standard numbering (track 0-34, sector 1-18) it bypasses the traditional floppy access method of spinning up the motor and stepping the head to the correct track. Instead it just calculates the absolute sector number and sends an LBA request to the controller.

This optimization does not look at the drive selects table, so it won’t honor any changes you have made with POKEs.  – Darren

Entirely new patches would be required for the current release of SDC-DOS 1.2. Or, perhaps, a much easier method is available…

I like easier. Let’s do it that way.

To be continued…

Word wrapping in Extended Color BASIC

2015/1/2 Update: Please see my follow-up article for two improved versions of this.

Last night, I began writing a program on my 1980s Radio Shack Color Computer. I am planning on going through hundreds of old floppy diskettes and copying them to disk image files.  The program I began writing will help automate this task.

I am using a new bit of retro hardware called the CoCoSDC. It acts like a floppy drive controller, but instead of writing to round pieces of magnetic plastic, it writes that data out to disk image files on an SD card. You can learn more about CoCoSDC in another series of articles I have been writing.

CoCoSDC is a brilliant piece of design work in that it detects and adjusts for the various models of Color Computers (1, 2 or 3) it is plugged in to. I decided my program should do the same, so my goal is to have one BASIC program that could be loaded on an original 1980 Color Computer 1 or 2, using its 32 column display, or run on a 1986 Color Computer 3, using the 40 or 80 column text screen. There are a few challenges I needed to solve:

  • The program should work on an original Radio Shack TRS-80 Color Computers or early model TRS-80 CoCo 2s, which had a 32 column display with uppercase character set. (On the Motorola 6847 VDG chip used by this machine, lowercase letters were represented by inverse uppercase characters.)
  • The program should take advantage of the later model Tandy Color Computer 2s that had an enhanced VDG chip that could display true lowercase. This option should still be available for earlier model CoCos which may have an aftermarket lowercase kit (like my old grey CoCo 1 has).
  • The program should work on a Tandy Color Computer 3, and let the user select between 32, 40 or 80 column display. This would allow it to worked hooked up to a crappy old TV display, or have nice 80 column text for users that had a good monochrome composite monitor or RGB-A color monitor.

I quickly designed and wrote a functional prototype, missing only a few features I had in mind. I started out by hard coding it to use a variable for the screen width so I could center text and and such. For example:

WD = 32
A$ = "CENTER THIS!"
PRINT TAB(WD/2-LEN(A$)/2);A$

By making that PRINT line a subroutine, I could call it anywhere I wanted to print centered text:

10 WD = 32 'SET SCREEN WIDTH
20 CLS 'CLEAR SCREEN
30 A$="IT WORKS!":GOSUB 1000
40 END
1000 REM PRINT CENTERED A$
1010 PRINT TAB(WD/2-LEN(A$)/2);A$
1020 RETURN
CoCo centered text.
XRoar emulator acting as a Radio Shack Color Computer 1.

I also made a routine to display a horizontal row of dashes so I could use that with nicely formatted menus.

Eventually, I needed to display some verbose text to the user, and knew I would need to make a routine to handle wrapping long lines. It’s easy to hard code PRINT statements when you know your screen width, but when it can vary, we can have the program do it for us.

Earlier in 2014 I implemented such a word wrap routine in C for a Raspberry Pi program I was designing. Unfortunately, porting that code to BASIC wouldn’t be possible since it took advantage of too many C features that have no BASIC equivalents. Instead, I had to design a new one.

Also, I wanted the option to pass in strings with upper and lower case and have them converted to uppercase. The only design restriction I enforced was to try not to do any string rebuilding. Interpreted BASICs have their own type of garbage collection (like you might find in Java and other modern languages) that do all kinds of memory shifting when you add two strings together. My program could be faster if it avoided that. (Not that it really matters, but good habits learned later in my programming career are difficult to break even when I am pretending it’s 1983.)

Here is the first version I came up with:

2015/1/5 Update: Please see my next article for two improved versions of this.

10 CLS 15 INPUT"SCREEN WIDTH [32]";WD:IF WD=0 THEN WD=32
20 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
25 TM=TIMER
30 A$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1000
35 PRINT"TIME TAKEN:"TIMER-TM
999 END
1000 REM WORD WRAP
1001 '
1002 'IN : A$=MESSAGE
1003 '     UC=1 UPPERCASE
1004 '     WD=SCREEN WIDTH
1005 'OUT: LN=LINES PRINTED
1006 'MOD: ST, EN
1007 '
1010 IF A$="" THEN PRINT:LN=1:RETURN
1015 IF UC>0 THEN FOR ST=1 TO LEN(A$):EN=ASC(MID$(A$,ST,1)):IF EN<96 THEN NEXT ST ELSE MID$(A$,ST,1)=CHR$(EN-32):NEXT ST
1020 LN=0:ST=1
1025 EN=LEN(A$)
1030 IF MID$(A$,ST,1)=" " AND ST<EN THEN ST=ST+1:GOTO 1030
1035 IF EN-ST>WD THEN FOR EN=ST+WD TO ST STEP-1:IF MID$(A$,EN,1)<>" " THEN NEXT EN ELSE EN=EN-1
1040 IF EN=ST THEN EN=ST+WD-1
1045 PRINT MID$(A$,ST,EN-ST+1);
1050 LN=LN+1
1055 IF EN-ST+1<WD THEN PRINT
1060 ST=EN+1:IF ST<LEN(A$) THEN 1025
1065 RETURN

Note: This is not my normal production programming style. I created this example for better readability, but in the future I will share some of the tricks we used at Sub-Etha to optimize BASIC programs to take up less memory and run faster. (They won’t be very readable…)

Word wrap, lowercase.
Word wrap, lowercase.
Word wrap, uppercase.
Word wrap, uppercase.

The subroutine at line 1000 expects the string to be in A$, the width of the display in WD, and the variable UC can be set to 1 to force the output to be in uppercase (which is slower). When it returns, LN will contain how many lines were printed. I also print out the TIMER so I can compare the speed of the routine, and see how much slower it is with uppercase conversion.

On the right are screen shots of the wrapped output with and without uppercase conversion. You can see that converting a few lines of text the way I did it took about five times longer.

There is one extra feature I should mention. One of the things that bothers me about many word-wrap routines I have seen is that they tend to ignore the final character of a line meaning that if you print a line that is exactly 40 characters long to a 40 column display, they usually wrap the last word even though it would have fit on the line. This is because, at least on all the systems I have experience with, when you print to the final character, the cursor moves to the start of the next line, then the PRINT command finishes and adds a carriage return. Here is an example of printing three 32 column lines on the CoCo:

10 CLS
20 PRINT"12345678901234567890123456789012"
30 PRINT"12345678901234567890123456789012"
40 PRINT"12345678901234567890123456789012"

You get something like this:

Printing without semicolon.
Printing without semicolon.

BASIC does not know that a line was skipped before it adds its own carriage return. You can prevent BASIC from adding a carriage return by adding a semicolon to the end of the PRINT line:

10 CLS
20 PRINT"12345678901234567890123456789012";
30 PRINT"12345678901234567890123456789012";
40 PRINT"12345678901234567890123456789012";
Printing with semicolon.
Printing with semicolon.

Most word wrap routines I have seen just don’t bother dealing with this, and never use the final character of the line. For my routine, I wanted a line that was exactly the length of the screen width to fit, so I always add the semicolon, then in line 1055 I print a carriage return if the line was shorter than the screen width (and thus BASIC didn’t already move the cursor to the next line).

Little things like this make the code bulkier than it needs to be.

Now I turn things over to you… This can be done much better. How would you implement a word wrap? And note there are several goals that would require different code:

  • Code size may be most important.
  • Execution speed may be most important.
  • You might want to use as few variables as possible.
  • You might want to avoid string manipulation.
  • You might not be able to alter any variables passed in (thus, if the user passes you A$, you are not allowed to change A$).

For me, I wanted to avoid string manipulation (for speed) and use fewer variables. Without that goal, I could have done the word wrap routine easier by making a copy of the string the user passed in and manipulating the copy. This is why my uppercase conversion makes changes to the string the user passed in. If I had the goal of not modifying what the user passed in, I would have had no choice other than making a copy in another string variable.

The choice is yours. Send in your best attempt and explain your goal. Here are the requirements:

  • A$ will be set to the string the user wishes to display.
  • WD will be set to the screen width.
  • UC will be 0 if the string is to be displayed as-is, or not 0 to convert to uppercase.
  • On return, LN will be the count of how many lines were displayed.

If you need a quick and easy emulator to run it in, check out XRoar. I have tips on how to get it running over at CoCopedia:

http://www.cocopedia.com/wiki/index.php/Using_XRoar

Have fun!

P.S. This example requires Extended Color BASIC. As it turns out, while the 1980 Color BASIC supports MID$(), you cannot use it to modify a string. Thus, A$=MID$(B$,1,1) would work, but MID$(B$,1,1)=”N” would not. My first CoCo had Extended Color BASIC so I never had to live without the extra features.

CoCoSDC for TRS-80 Color Computer part 4

See also: part 1part 2part 3part 4, part 5 and part 6.

After about a decade in storage, I have finally gotten my Tandy Color Computer 3 set back up. Based on the 2004 copyright of the last item I purchased for it – a Cloud-9 SuperIDE hard drive interface – I guess about ten years ago was the last time I did anything with it. I bought that interface because it has a CompactFlash slot on the side of it, and I liked the idea of using a 128MB memory card as a hard drive rather than big, clunky physical drives. I recall being at a Chicago CoCoFEST! (like the one coming up in April 2015) and leaving the convention site to go buy a CF card, and then spending the evening copying data from my old SCSI SyQuest EZ135 drive disks to it.

I had stopped using the CoCo 3 as my primary computer in 1996, though I still had it set up (and may have even still been running an OS-9 BBS on it) for a year or so more. It’s hard to believe twenty years have passed since the days of 8-bit computers.

Over Christmas break, I set up my CoCo 3, Multi-Pak, floppy drives and SCSI hard drive on a small computer desk in my livingroom next to my TV. There was no room left for a monitor on this modern desk. 80s-style desks usually had an upper shelf for the monitor since old systems took up most of the desk with the computer and external accessories. I guess I forgot how unreadable 80 column text was on a TV via composite video, so I spent another night getting out my 80s computer desk so I could have a spot for my CM-8 monitor. Unfortunately, I had hacked the monitor cable so I could also use it with my MM/1 and it I may have a wire loose – the colors do not work properly (no red, it seems). At least I can read the (off colored) 80 column text now.

I have now spent a bit of time playing with the CoCoSDC. It is significantly faster than a real floppy drive. As a test, I created a new blank disk image as DRIVE 1, and then make DRIVE 0 access my real floppy drive so I could do a backup:

DRIVE 1,"MYDISK",NEW
DRIVE 0,OFF
BACKUP 0 TO 1

I would listen to the physical drive click and clack as it read data for about five seconds, then see the red LED on the CoCoSDC clip for a mere moment as it wrote to the disk image file on the SD card. Wow – fast.

I also did some tests of booting OS-9 from a real floppy versus a virtual floppy. CoCoSDC blazed through the NitrOS-9 boot screen as if it were booting off a hard drive. I believe I read that it’s still not as fast as the fastest hard drive interfaces, but it’s still quite impressive.

I began writing a quick test program in BASIC that would prompt me to type in an disk image name, and then it would create that on DRIVE 1 and prompt me to do the backup. I wanted to automate my floppy archiving so all I had to do was insert floppy, type a disk image name, and press ENTER. I wanted it do do something like this:

10 LINE INPUT "DISK NAME: ";DN$
20 DRIVE 1,DN$,NEW
30 PRINT "PRESS ENTER TO BACKUP 0 TO ";DN$;
40 LINE INPUT A$
50 DRIVE 0,OFF
60 BACKUP 0 TO 1
70 GOTO 10

Note: I wouldn’t actually write it that way — I just simplified it for this example.

I ran the program and it worked for the first backup then the program ended. Where was my GOTO?

I had forgotten that in Disk Extended Color BASIC, the BACKUP command clears out the BASIC program so it can use that memory for the disk copy buffer. Oops! No using the BACKUP command in a BASIC program. (On the CoCo mailing list. Robert Gault mentioned that RGB-DOS and HDB-DOS both patched BASIC so this does not happen. Most of my final years using the CoCo were with these alternate DOSes so perhaps that is why I was surprised.)

To solve this, I will either have to write my own backup routine in assembly that I call from my BASIC program, or see if I can find another way to do this from BASIC. I will show you that in the next installment.

To be continued…

CoCoSDC for TRS-80 Color Computer part 3

See also: part 1part 2part 3part 4, part 5 and part 6.

Today, my project to archive all my old Radio Shack TRS-80 Color Computer floppies continues. I will be using Darren Atkinson’s amazing CoCoSDC interface to copy from physical 5 1/4″ floppy disks to disk image files on an SD memory card, all from a computer that first came out in 1980. If you are just joining me, please read the two earlier parts for an introduction.

My previous article concluded with me describing an easy way to backup standard Color Computer floppy disks. Due to limits of affordable floppy drive hardware at the time, the Color Computer’s Disk BASIC ROM was only written to support a 35-track floppy drive. As technology improved (and prices got lower), Radio Shack would switch from using old full-height, belt-driven 35 track floppy drives to half-height 40-track drives. However, to maintain backwards compatibility, Disk BASIC was never updated to use those extra five tracks, meaning that whether you used the first CoCo disk drive introduced in 1981, or the last one sold in 1991, Disk BASIC still treated it as a 35-track, single-sided device.

Almost as soon as 40-track drives were hooked up to a CoCo, users went to work figuring out how to make use of the extra storage potential. Simple patch programs were released that modified Disk BASIC so it could use all 40 tracks. With this patch, you could still read and write to an original 35-track disk, but if you formatted and wrote to a 40-track disk, only users running the same patch could read it. Because of this, through the entire history of the CoCo, virtually all Disk BASIC software released on floppy disk was in the 35-track format.

I was one of those users, so many of my old CoCo disks are 40 tracks. Since the floppy drive hardware emulated by CoCoSDC emulates the original floppy drive interface, the same patches should work to set CoCoSDC Disk BASIC to 40-track mode and then these floppies can be backed up the same way. (Basically, the patch would change the upper limit from 35 to 40, so formatting with the DSKINI command or using the BACKUP command would now access all 40 tracks instead of just 35.)

Problem: If by default, creating a new .DSK image with the DRIVE command makes a 35-track .DSK file, how can you make a 40-track .DSK file?

According to responses from the CoCo mailing list, the designer Darren says you can download  a blank 40-track .DSK image file and mount and use it. But, he also said that images will automatically expand if you write past the 35-tracks. Thus, if I have loaded my patches that set Disk BASIC to 40-tracks, then I create a new blank 35-track .DSK image and BACKUP to it, once it writes past the 35th track, it should start expanding the file to become a 40-track image file.

I will be testing this, soon.

Bigger problem: Early on, users figured out that some floppy drives actually could actually write past 40 tracks and get a bit more storage. I tested this on the drive I had at the time, and found I could reliably write up to 42-tracks of data. Somewhere in my archive are old, old floppy disks formatted to 42-tracks. Will CoCoSDC work with these?

I will be testing this soon, too. I hope to find a way to do this entirely from the CoCo without having to put the SD card in a PC/Mac and work with it.

More to come…

CoCoSDC for TRS-80 Color Computer part 2

See also: part 1part 2part 3part 4, part 5 and part 6.

In my previous article, I ran through a brief history of disk drives for the Radio Shack TRS-80 Color Computer (“CoCo”). This was intended to give a better understanding to the significance of Darren Atkinson’s CoCoSDC floppy drive replacement.

CoCoSDC was designed to fit in to the FD-502 disk drive cartridge enclosure, so it has mounting holes in the same locations as the original controller. For those who do not have a dead FD-502 controller (or, like me, do not want to gut a perfectly good one), Tim Lindner has produced the CoCoSDC enclosure, which is two pieces of cut plexiglass with some mounting screws and standoffs. I have received my CoCoSDC from Ed “Zippster” Snider, and the enclosure parts from Tim (see photo). Ed was offering the CoCoSDC assembled for $40, or $30 in a kit. The enclosure is $10 (or a few bucks more assembled). For a total of $50 plus shipping, it is a great value (SD card not included).

The CoCoSDC has some flash storage which appears to the CoCo as a ROM, the same as the original ROM chips on a real disk drive (or Program Pak) cartridge. Although CoCoSDC could run with the original Disk Extended Color BASIC ROM, it would be very limited since you would have to configure the SD card on a PC/Mac with to disk images that would appear as Drive 0 and Drive 1 to the CoCo. It would be like having a dual-drive floppy system that you couldn’t switch diskettes! Instead, Darren modified Disk BASIC so you could select which disk image should be used for either DRIVE 0 or DRIVE 1. (The CoCoSDC can only act like those two drives.)

New BASIC commands (or rather, additions to existing commands) allow you to mount existing images from the SD card (just like inserting a floppy disk) or create new ones. For most “normal” stuff, you use a .DSK image, which is basically just an image file containing a bunch of 256-byte sectors that match the normal sectors of a floppy disk.

The original 1981 CoCo floppy drive was a 35-track drive with 18 sectors per track. Each sector was 256-bytes. Thus, a disk image of an original 1981 disk would be about 160K (161,280 bytes = 256 * 18 * 35) and represent 630 sectors. A 40-track disk would be 180K (184,320 bytes = 256 * 18 *40). A double-sided 40-track disk would be 360K (368,640 bytes = 256 * 18 * 40 * 2) and a double-sided 80-track disk would be 720K (737,280 bytes = 256 * 18 * 80 * 2).

The size of the .DSK file on the SD card tells the CoCoSDC what to do with it. Basically, if the file you try to mount is 737,280 bytes or less, it treats it as a bunch of standard sectors. For software that used the standard disk format (256-byte sectors), this works fine.

However, not all software used the standard disk format. Some software chose to use non-standard sector sizes, which was useful for getting more user storage on a diskette or, more commonly, for copy protection. A programmer could have a portion of the disk in normal sector format so Disk BASIC could load some booter code, then that code could bypass Disk BASIC and talk to the drive controller chip directly to seek to other parts of the floppy disk and read tracks with non-standard sector sizes. This provided a simple method of copy protection since Disk BASIC did not handle non-standard sector sizes. Attempting to BACKUP one of these copy protected disks would fail in Disk BASIC.

Special programs, like The Defeater by Carl England, were written that could recreate the special sectors and make a clone of any copy protected disk. The Defeater has also been used to make virtual disk images of copy protected diskettes so they can be used in CoCo emulators and, now, with the CoCoSDC.

CoCoSDC calls these .SDF images and they contain much more than just a series of 256-byte sectors. A full description of the format is on Darren’s CoCoSDC page. SDF is basically an update to an earlier .DMK format created for use with CoCo emulators, but Darren restructured it so it could be implemented with the limited RAM on the CoCoSDC processor. Darren provides a “dmk2sdf” program you can use to convert emulator DMK files to SDF files that can be loaded on a CoCoSDC SD card.

For my project, my first goal is going to be to clone all my legacy floppy disks to a CoCoSDC SD card. Most all of my floppies are normal 35-track disks so they will end up as .DSK files. I also plan to use The Defeater to make images of all my copy protected CoCo software to .SDF files as well. (I’ll have to write a separate article on that. I have had The Defeater ever since Carl showed it to me at a CoCoFEST, but I have never actually used it.)

To do this, I will need both my floppy controller and CoCoSDC plugged in at the same time, which means I need to use a Multi-Pak. The software in CoCoSDC is able to access the real floppy drive interface, and mix real floppy drives with virtual SD floppy drive images.

By default, CoCoSDC boots with all the drives being virtual SD drives. For my situation, I want Drive 0 to be my real CoCo floppy drive, and Drive 1 to be a CoCoSDC virtual drive. I want to turn off the virtual drive 0:

DRIVE 0, OFF

The “DRIVE” command is part of Disk BASIC. Normally, it is used to set the default drive to use for commands like “DIR” and “LOAD”. If I ran to load a program from drive 2, I would have to type LOAD “PROGRAM.BAS:2”, but if I first type DRIVE 2, all future commands will assume drive 2, thus:

LOAD "PROGRAM.BAS:2"

…could instead be done as:

DRIVE 2
LOAD "PROGRAM.BAS"

Darren has expanded this command so you can put “,ON” or “,OFF” after it to toggle the status of a virtual disk. After typing “DRIVE 0,OFF”, if I type “DIR 0” I should see my physical floppy drive 0 be listed.

To set a virtual drive, Darren has expanded the command to look for a quote character and a filename on the SD card. If I have a pre-created .DSK image called “BASIC.DSK” on the SD card, I could mount it as drive 1 by typing:

DRIVE 1,"BASIC.DSK

From that moment on, doing any access to Drive 1 would show the content of the BASIC.DSK file. However, in my case, I do not yet have any disk images. I can create one by adding the “NEW” keyword:

DRIVE 1, "BASIC.DSK",NEW

This should create a new file called “BASIC.DSK” (a 35-track singled sided disk image) in the root directory of the SD card. I could then insert a real floppy in to drive 0 and back it up to the virtual disk:

BACKUP 0 TO 1

When the BACKUP is complete, I can swap out my floppy disks and insert to the next one. To swap out the virtual drive, Darren added an “UNLOAD” option:

DRIVE 1,UNLOAD

I could then create my next disk and do a backup.

If all I had were standard 35-track Disk BASIC disks, this would be fine (if a bit time consuming with all the typing). A small program could be written to help automate this. It might ask you to type a short name for the disk you are inserting (8 characters or less, to match the filename of the .DSK image file) and then have you press ENTER to begin doing the “BACKUP 0 TO 1”. I will be writing (and sharing) such a program, soon.

Up next: 40 track floppies and other issues to solve.

CoCoSDC for TRS-80 Color Computer part 1

See also: part 1part 2part 3part 4, part 5 and part 6.

2014/12/30 Update: Tim Lindner corrected me. CoCo drives started out as double density, not single density. Corrections have been made. That means the 80 track 5 1/4″ drives I had were quad density. Thanks, Tim!

After a series of issues with the U.S. Postal Service with multiple shipments to multiple addresses, I finally have received my CoCoSDC and enclosure kit. As previously mentioned, CoCoSDC is a floppy drive controller for the Radio Shack TRS-80 Color Computer that emulates the original disk controller but instead of accessing physical floppy drives, it accesses data on an SD memory card.

First, let’s discuss a bit of CoCo disk drive history…

The Radio Shack TRS-80 Color Computer (later nicknamed “CoCo”) was introduced in July 1980 as a 4K computer that hooked to a TV set. The computer had a TV output (like an Atari 2600), and ports for a cassette cable (for hooking to a cassette recorder to load or save programs), serial port (for hooking up to a printer or modem), and two joystick ports. A cartridge port was provided for running software from “Program Paks” or future hardware expansion.

One such future hardware expansion was the Disk Drive controller (catalog number 26-3022) released in 1981. Although there were already some third party disk drive products, once Radio Shack released an official implementation, it would set the standard and other third parties would release their own compatible clones in coming years (J&M, Hard Drive Specialists, etc.).

The disk drive interface plugged in to the CoCo’s cartridge port and had a 34-pin edge connector coming off of it. A ribbon cable would then connect to standard 5 1/4″ floppy drives. The original Radio Shack unit came with a full height, single sided, 35-track, belt-driven 5 1/4″ floppy drive which provided 156K of storage on a single double density diskette. Up to four single-sided disk drives were supported by the interface and the new Disk Extended Color BASIC which was contained on a ROM chip inside the cartridge. (Or, three double sided drives, but no one had those yet.)

Electornically, the interface could support double sided drives with up to 80 tracks (720K). This would later allow the use of 720K double density 5 1/4″ floppy drives, as well as 3 1/2″ drives (introduced in 1983, and made popular by the 1984 release of the Apple Macintosh). Thank goodness for compatible hardware standards (back then).

I found a good rundown of the Radio Shack floppy drives at Techno’s CoCo Floppy Page. Here is a brief summary:

Over the years, it appears Radio Shack released five versions of the disk drive interface. The original required 12V to operate (which the first Color Computer model provided, but later CoCo 2 and 3 models did not provide). Using it on a CoCo 2 or 3 required using a Multi-Pak expansion device, which plugged in to the cartridge port and provided four selectable cartridge slots (as well 12V power for older devices). After that, there was the FD-500 (26-3129) which was a 5V version of the original, followed by the FD-501 (26-3131, released in 1986 with a shorter cartridge) and the FD-502 (1986, 26-3133).

Besides circuit board changes, each release of the disk interface came with different floppy drives. The first two came with vertical full-height single sided drives. The FD-501 and FD-502 came with half height drives in a horizontal case with a slot to add a second drive later. The final FD-502 release was special, as it included a double sided drive, even though (for compatibility reasons), the Disk Extended Color BASIC still treated it like a single-sided, 35 track device. Patches were created to make Disk BASIC use more of the disk space, but disks written in that format could only be readable by other users running the same patches. Alternative operating systems like Flex and Microware’s OS-9 also could make full use of the storage.

On my personal CoCo setup, I eventually had a standard 40 track double sided single double-density (DSSD DSDD) for my primary drive, and two other 80 track double-sided double quad-density (DSDD DSQD) 5 1/4″ floppies for storage. This gave my OS-9 BBS a massive 1.8 megabytes of storage! (I could have had up to 2.1mb if I had an 80 track drive for my boot disk, but I wanted one drive that was 100% compatible with stock formats). Later, I replaced my 5 1/4″ double density drives with 3 1/2″ drives (though I always had to keep a 5 1/4″ around, since the standard format software was sold on was still that).

Over the years, other store options became available with the introduction of hard drive interface cartridges from Radio Shack and third parties. I used several in my days, starting with a Burke & Burke interface that would use RLL or MFM hard drives, then moving to a KenTon SCSI interface and RGB-DOS (patches to Disk Extended Color BASIC which let you access the hard drive as if it were up to 256 floppy disks).

The introduction of the Iomega Zip drive (with swappable diskettes that stored 100mb each – wow!) was a problem, since the KenTon interface did not generate hardware parity which the Zip drive required. Because of this, I ended up using the competing SyQuest EZ-135 drives (which were faster, and stored more, but ultimately lost the format war to Iomega).

My final hard drive interface was the SuperIDE from Cloud-9. I never actually had an IDE hard drive hooked to my computer, but I did make use of the CompactFlash slot on the interface and used a CF card for my hard drive.

That’s quite the evolution from the original 156K floppy drive of 1981!

But I digress. This article is about the CoCoSDC. As you can see, over the years, floppies became less important as hard drive solutions became available. Many advanced CoCo users might only have two floppy drives (one for compatibility and booting, and possibly a second to make floppy backups easier). The need for 80 track floppies went away once low-cost hard drive options were available.

So why am I so excited about the CoCoSDC? It finally replaces the need for a physical floppy drive for anything but reading old media. Darren Atkinson designed the CoCoSDC to emulate the original CoCo floppy controller, so when it is plugged in to a CoCo, any software designed to access the floppy hardware will still work. This may seem like an obvious statement, but over the years, attempts to bring hard drive support to old systems have always had compatibility limitations. For instance, while Disk BASIC could be patched to allow the user to access virtual floppies on a hard drive, any software that was written to access the floppy controller interface directly would still try to do that, and would never run through the hard drive interface. Only software that used the native Disk BASIC floppy access routines had any hope of running from a virtual floppy drive, and even then, while RGB-DOS patched basic to let you have 256 virtual floppies, even compatible software might only let you type in “0-3” for the drive to use so it might never let you use those extra drives.

CoCoSDC solves this by pretending to be the original Western Digital 1774 drive controller chipset to the CoCo. This makes it effectively 100% compatible with any software that ever ran from a Radio Shack floppy drive. However, there is still one limitation I can see. CoCoSDC can only map two floppies at a time, so if you actually had software hard-coded to require three or four floppy drives to run, you would be out of luck. (I believe this limitation is caused by the available RAM in the processor on the CoCoSDC.)

In part 2, I will discuss why all of this is incedibly important.