Monthly Archives: May 2023

Huffman K1 Librarian, my first commercial product

Although I was “this close” to having my *ALLRAM* BBS sold by a well-known CoCo company back in 1983 it wasn’t until 1989 that something I wrote appeared for sale in the pages of Rainbow Magazine.

Rainbow Magazine, November 1989, Page 111.

I had gotten my first musical keyboard (a Suzuki Keyman PK-61) during high school, and then acquired a Casio CZ-101 synthesizer. After graduation in 1987, I purchased the CoCo MIDI interface from Rulaford Research. This started my love for MIDI and creating keyboard music which I still enjoy today (just without MIDI, as it’s been replaced by virtual instruments on a computer and USB piano keyboards).

There were two main types of MIDI programs. A sequencer allowed recording the keys pressed on a MIDI keyboard and playing them back. This was a high-tech player piano, but instead of a roll of paper with holes punch in it triggering hammers hitting strings, it was serial byte codes going to a synthesizer or sound module playing notes. Lester Hands’ sequencer was quite an achievement for a 64K CoCo.

The second type of program was called a librarian. These programs would use special messages the synthesizer supported to download sound data (the “patch” or “voice” as keyboards called them) and save it to tape or disk. You could later upload that information back. This allowed saving out all the sounds a keyboard made, and loading in new ones. Or, backing up custom sounds you created.

There was a third type know as an editor, but I never had any of those so I cannot really comment on them.

I recall buying a Casio CZ-101 Librarian from Rulaford Research.

I eventually saved up enough to buy a Kawai K1 full-size synthesizer. I learned enough about how the CoCo MIDI hardware pak worked (thanks to my Commodore friend Mark finding the data sheet) to create routines to read and write data through it. This, and some technical information on the SysEx (system exclusive) MIDI messages of the K1 led me to create a librarian for that synth. I had been in communication with Cecil Houk of Rulaford Research and he suggested putting my name in the title so it would be an instantly unique title (rather than something generic like “Kawai K1 Librarian”).

The end result was the Huffman K1 Librarian, shown here at version 1.2. I have no recollection of what changed between 1.0, 1.1 and this version.

At the time, while many of us had copies of software we did not purchase, I didn’t like having copies of anything that asked me to not copy it. I included the message “Support the future of music on the Color Computer. Please do not pirate this program.” on the title screen.

The main menu allows sending a single patch (voice) to and from the K1, or a block (which was a bank of many patches).

I do not recall much about how the K1 LCD screen looked, but I know it used an uppercase “I” or “E” (internal/external) as well as lowercase “i” or “e” for the singles voices.

The voices were divided up in to four banks (lettered A to D) with 8 patches in each. It was basically octal! This is the only time I’ve seen base-8 numbering used (though in this case, it would be like A1-A8 to D1-D8 rather than 00-38 in octal).

To dump (upload) a patch to the synthesizer, you had to type the name of the patch file. I built-in a Directory command similar to how the Casio Librarian did it.

This may have been the first time I ever made use of DSKI$ to manually parse the directory table of the CoCo’s disk format.

I also added a disk menu to give a fuller directory, kill files, rename files, and copy files.

Here was my copyright notice in REMarks at the top of the program. From looking at the CLEAR command, that reminds me that my assembly language MIDI routines loaded at &H7000.

Also note I set a variable DR (current drive) with a PEEK, rather than hard-coding a default of drive 0. This meant if the user had done a “DRIVE 1” and was running it from that drive, it would default to using drive 1. I had forgotten about this technique.

I guess I typed too soon. At the end of my programmer version notes, so now I know about 1.1 (add error checking) and 1.2 (more error checking). Nice.

Beyond looking at the menu screens, today I have no way to do anything with this program. I sold my Kawai K1 long ago, and upgraded to a K4 :) I eventually sold that and replaced it with a Yamaha W7.

Humble beginnings, and fun times. I hope you enjoyed this look back at my earliest commercial product.

Until next time…

TIL: You can build C in Microsoft Visual Studio

I feel dumb for not knowing this, but I was under the impression that today’s Visual Studio only built things like C#, C++, etc. — since those are the things listed when you make a new project.

I happened to ask ChatGPT about building C in Studio, and it told me I could just make C++ project and save the file out as a .c and get a C project. I had no idea.

#TheMoreYouKnow

Though, it’s many more steps to do this than, say, popping out to https://www.onlinegdb.com/online_c_compiler, just to do a quick test, but it’s useful.

I have only tried this under Windows, but I plan to see if the Mac version of Visual Studio supports the same. I’ve tried to get C code building in VS Code, but it’s klunky (makefiles!).

DATA problems revisited

Awhile back, I posted a note about some weird behavior with DATA statements in Color BASIC. The issue was that you really couldn’t have anything else on a line after the DATA keyword other than data.

I recently mentioned this to Alex Evans so he could make sure his BASIC utilities were not combining DATA statements together. He mentioned something to me that demonstrated this issue even more:

100 DATA 1,2,3:PRINT "FOO"
110 DATA -1

If you RUN that program, it should print “FOO” and do nothing else, proving that the interpreter does scan the entire line looking for things after a DATA statement.

BUT, if you try to USE that DATA, it will continue reading after the 3 and error out, since the data it finds is not an ASCII number:

Above, the “?SN ERROR” is caused by trying to read a numeric variable from what is NOT a string (the token value for PRINT followed by FOO in quotes). Altering the program to use A$ instead of A (and print one item read per line) shows this a bit better:

Alex explained it like this:

Basically, for the purposes of READ everything after the DATA keyword is part of the data statements, but the interpreter executes the line properly.

– Alex Evans

You can see it appears to treat the colon as a DATA separator, since it does not appear as part of the read DATA.

This is a quirk I do not expect many of us ever encountered, since most of us probable grouped all our DATA together, without mixing commands in with them:

100 DATA 1,2,3,-1

I bet all of this was discovered and covered back in the 1980s in various CoCo magazines and newsletters, but the behavior surprised me when I stumbled upon it so I guess I never saw it.

Until next time…

Inverse/reverse text on the CoCo

The later model CoCo 2s used an upgrade MC6847 video display generator chip. This chip provided an updated font with true lowercase, as well as the ability to change the border color and inverse the video.

The original 6847 had a hardware pin that could inverse video, and I recall taking the VDG out of my CoCo 1 to pull up a pin then re-insert it. That gave me the nice inverted display I often use in the XRoar emulator:

In the nearly-forty years since I did that modification I had forgotten that it could only be done in hardware. I thought there was probably a POKE or something, since many programs I had used the inverted video mode.

I couldn’t figure it out, and the bits that made the updated 6847T1 show inverse video did not work on the original 6847.

So I asked on the CoCo mailing list

I wrote a program to go through the 6847 T1 VDG text modes, which includes lowercase, changing the border, and inverting the screen.

I’ve seen programs on the CoCo (like GRABBER) that invert/reverse the screen in software, but the bits to do that on the T1 don’t seem to do anything on the 6847.

Does anyone know of a reference that would show me how to do this? Thanks, much!

– Me, 2/15/2023

L. Curtis Boyle replied:

On the regular VDG, there is no reverse video bit. So you have to use the inverted characters in the character set (ie POKE 1024+loc,0-63).

L. Curtis Boyle, 2/15/2023

It was Tim Lindner that reminded me of the hardware pin:

There is an inverse video pin on the chip. But it is not hooked up. I think.

– tim lindner, 2/15/2023

With that in mind, I decided to do some quick tests. This test PRINTs every possible visible character on the screen:

10 CLS:PRINT
20 FOR C=32 TO 255:PRINT CHR$(C);:NEXT
30 GOTO 30

I skipped the first line and I started printing with CHR$(32) (a space) because the values below that are non-visible characters.

BUT, if you POKE to the screen, you get more characters. Here is an update that will POKE the values 0-255 to the bottom half of the screen:

10 CLS:PRINT
20 FOR C=32 TO 255:PRINT CHR$(C);:NEXT
30 FOR C=0 TO 255:POKE 1280+C,C:NEXT
40 GOTO 40

Comparing the PRINT versus POKE characters, you can see PRINT has 64 ASCII-printable characters starting at 32-96. After that are 32 inverted characters. There are 32 characters that you cannot print in inverse.

When you POKE, values 0-63 represent the inverted full ASCII character set, and 64-127 are the uppercase.

 PRINT   POKE
------- -------
32-96   64-127  Space to Left Arrow
96-127  0-31    Inverse "@" to Inverse Left Arrow
        32-63   Inverse Space to Inverse "Left Arrow"?"

From that table, you can see it’s impossible to PRINT thirty two of the available inverse characters in the 6847, but you can POKE them.

I thought it might be fun to write a routine that would PEEK through the screen memory and invert all the characters. Since there are 32 non-inverted characters from 32-96, and 32 inverted characters from 96-127, it seems all I needed to do is PEEK each location and if it was from 96-127, subtract 64 and POKE it back.

We start with this slow program, complete with some benchmarking timing code:

10 ' FILL SCREEN WITH STUFF
20 CLS:FOR C=32 TO 255:PRINT CHR$(C);:NEXT
30 TIMER=0
40 FOR L=1024 TO 1536
50 V=PEEK(L):IF V>63 AND V<128 THEN POKE L,V-64
60 NEXT
70 PRINT TIMER

This prints 631 for me. One of the first speedups we can do is change the decimal numbers in line 50 to hex.

10 ' FILL SCREEN WITH STUFF
20 CLS:FOR C=32 TO 255:PRINT CHR$(C);:NEXT
30 TIMER=0
40 FOR L=1024 TO 1536
50 V=PEEK(L):IF V>&H3F AND V<&H80 THEN POKE L,V-&H40
60 NEXT
70 PRINT TIMER

That simple change takes the time down to 490. And, since variable lookups (when there aren’t a ton of variables) is faster than parsing a hex number in source code, we can change those values to variables and make it even faster:

10 ' FILL SCREEN WITH STUFF
20 CLS:FOR C=32 TO 255:PRINT CHR$(C);:NEXT
25 X=&H3F:Y=&H80:Z=&H40
30 TIMER=0
40 FOR L=1024 TO 1536
50 V=PEEK(L):IF V>X AND V<Y THEN POKE L,V-Z
60 NEXT
70 PRINT TIMER

This drops to 475.

And, any time you use “IF this AND that”, it can be sped up by doing “IF this THEN IF that”. Let’s try that…

10 ' FILL SCREEN WITH STUFF
20 CLS:FOR C=32 TO 255:PRINT CHR$(C);:NEXT
25 X=&H3F:Y=&H80:Z=&H40
30 TIMER=0
40 FOR L=1024 TO 1536
50 V=PEEK(L):IF V>X THEN IF V<Y THEN POKE L,V-Z
60 NEXT
70 PRINT TIMER

In this case, it really did not change anything — I see 474. Okay, that was a fail. I guess it doesn’t always help.

Since the math we are doing is a subtract 64, we should see if we can do that by using AND to remove the bit that represents 64. We don’t want to do that if it is a graphics character (128-255) so we’ll need to retain on IF:

10 ' FILL SCREEN WITH STUFF
20 CLS:FOR C=32 TO 255:PRINT CHR$(C);:NEXT
25 X=&H3F:Y=&H80:Z=&H40
30 TIMER=0
40 FOR L=1024 TO 1536

50 V=PEEK(L):IF V<Y THEN POKE L,V AND &H3F
60 NEXT
70 PRINT TIMER

That shows 367 for me. Better!

I did test to see if checking for the 8th bit (V AND 128) was faster than just comparing a value (A > 127) and the “>” check was faster, so we’ll stick with that.

At this point, the routine to inverse the screen while leaving graphics characters alone is getting close to twice as fast as the original version.

Can we do better? At least a bit, by removing spaces and combining lines (and even though it only parses the FOR values once, I’ll switch them to hex):

10 ' FILL SCREEN WITH STUFF
20 CLS:FOR C=32 TO 255:PRINT CHR$(C);:NEXT
25 X=&H3F:Y=&H80
30 TIMER=0
40 FORL=&H400 TO&H5FF:V=PEEK(L):IFV<Y THENPOKEL,V AND&H3F
50 NEXT
70 PRINT TIMER

That prints 356. (There were a few spaces that could not be removed due to needing them so the BASIC parser knows where a variable ends and a keyword begins.)

Now we could make a subroutine that would invert the screen, slowly.

1000 ' INVERT SCREEN
1010 X=&H3F:Y=&H80:FORL=&H400 TO&H5FF:V=PEEK(L):IFV<Y THENPOKEL,V AND&H3F
1020 NEXT:RETURN

While it might be a neat “effect” for a title screen to watch it painting the screen, it’s not fast enough to use in a program that you want the output to always be inverted.

Some thoughts…

If no semigraphics characters were being used, the check for them could be removed:

10 ' FILL SCREEN WITH STUFF
20 CLS:FOR C=32 TO 127:PRINT CHR$(C);:NEXT
25 X=&H3F:Y=&H80
30 TIMER=0
40 FORL=&H400 TO&H5FF:V=PEEK(L):POKEL,V AND&H3F
50 NEXT
70 PRINT TIMER

That prints 304 — twice as fast as the original, though it will corrupt semigraphics blocks by changing them.

Another approach might be to only do the portion of the screen that has been PRINTed to by changing the range that the L loop scans. For example, you could call a routine to invert just the last line of the screen every time something is PRINTed there.

Or, perhaps a custom “print reverse” routine might make more sense. We’d also want a special CLS routine that cleared the screen to inverse spaces.

10 CLS
20 GOSUB 1100
30 P=0:A$="This is in REVERSE video!":GOSUB 1000
40 P=32:A$="And this one is, too.":GOSUB 1000
50 P=480:A$="Hello, bottom line!":GOSUB 1000

999 GOTO 999

1000 ' PRINT@ REVERSE
1010 L=&H3FF+P:FOR I=1 TO LEN(A$)
1020 V=ASC(MID$(A$,I,1)):IF V>95 THEN V=V-96 ELSE IF V>64 THEN V=V-64
1030 POKE L+I,V:NEXT:RETURN

1100 ' CLS REVERSE
1110 P=0:FOR L=&H400 TO &H5FF:POKE L,&H20:NEXT:RETURN

To use the function set P to the PRINT@ location, assign the string to A$, then GOSUB 1000. Not much to it.

Handling scrolling the screen would be more work (and slower), but could also be done. Perhaps the routine could track the PRINT@ position internally, so every time you print it increases P by 32 (next line) and if it gets to the end of the screen, it could scroll everything up… slowly…

10 CLS
20 GOSUB 1200
30 A$="This is in REVERSE video!":GOSUB 1000
40 A$="And this one is, too.":GOSUB 1000
50 A$="PRINT;":GOSUB 1100
60 A$="NEXT TEXT":GOSUB 1100
70 P=480:A$="BOTTOM LINE":GOSUB 1000
80 A$="AND THIS":GOSUB 1000

999 GOTO 999

1000 ' PRINT@ REVERSE
1010 GOSUB 1100:P=P+32-I+1:IF P<512 THEN 1050
1020 ' SCROLL
1030 FOR L=&H400 TO &H5E0:POKE L,PEEK(L+&H20):NEXT
1040 FOR L=&H5E0 TO &H5FF:POKE L,&H20:NEXT:P=P-32
1050 RETURN

1100 ' PRINT@; REVERSE
1110 L=&H3FF+P:FOR I=1 TO LEN(A$)
1120 V=ASC(MID$(A$,I,1)):IF V>95 THEN V=V-96 ELSE IF V>64 THEN V=V-64
1130 POKE L+I,V:NEXT:P=P+I-1
1140 RETURN

1200 ' CLS REVERSE
1210 P=0:FOR L=&H400 TO &H5FF:POKE L,&H20:NEXT:RETURN

To clear the screen, GOSUB 1200.

To do a normal PRINT, set the string in A$ and GOSUB 1000.

To do a PRINT with a semicolon at the end, set the string in A$ and GOSUB 1100.

To simulate a PRINT@, set P to the screen position then GOSUB 1000 or 1100.

This program does NOT handle printing past position 512 at the bottom right of the screen.

IT CAN POKE PAST SCREEN MEMORY AND CAUSE A CRASH!

A simple check could be done before line 1130 to see if L+I is going to be past the end of screen memory (1536) and adjust accordingly.

If there is any interest (leave a comment), we can look at how we might do that in a future article.

Is this useful?

If we really wanted to use inverse video in BASIC, I guess it would be useful. Can you make it faster in BASIC? What would you suggest? Leave a comment.

But, doing inverse text would be a simple thing for some assembly language routines that could be called easily from BASIC.

OR, perhaps there would be a simple way to just hook in to the output vector used by Color BASIC and do it there.

To be continue… Maybe.

Optimizing BASIC for speed versus size

Normally, I would say that “optimizing” a program could mean two things:

  1. Making the program as fast as possible (even if it was larger)
  2. Making the program as small as possible (even if it was slower)

There is always a compromise.

Loop Unrolling

In BASIC, this will print 100 times:

10 FOR I=1 TO 100
20 PRINT I
30 NEXT

But this might be a faster way to print 100 times:

10 PRINT 1
20 PRINT 2
30 PRINT 3
...
1000 PRINT 1000

This is a poor example because PRINTing a variable is generally faster than printing a number due to the BASIC interpreter needing to parse all the TEXT digits of the number and convert it to a string to PRINT. To my surprise, when I ran this test, I found they were basically the same speed. The overhead of processing a line and converting a string of digits seemed to offset the time saved of the loop that was printing a variable that doesn’t need parsing. (FOR/NEXT loop fast, PRINTing variable fast versus a bunch of lines that might be faster but have slower parsing for the text digits.)

However, if the series of individual PRINT lines were printing a variable (“PRINT X”) it would be more than twice as fast.

10 PRINT X
20 PRINT X
...
1000 PRINT X

Apples to apples (difficult to do in this example), blasting out a ton of lines is faster than doing them in a loop. I’ve heard assembly language programmers refer to this as “loop unrolling” and it can work in BASIC as well.

A better example might be how you draw a background screen. Consider a 32 column text screen that is cleared to black, and a border is printed around it:

Here is some code that would do this:

10 CLS 0
20 PRINT CHR$(128);STRING$(30,207);CHR$(128);
30 FOR I=1 TO 14
40 PRINT CHR$(207);STRING$(30," ");CHR$(207);
50 NEXT
60 PRINT CHR$(128);STRING$(30,207);
70 GOTO 70

Or, you could do it using loop unrolling and change that loop of 14 steps to be 14 PRINTS:

10 CLS 0
20 PRINT CHR$(128);STRING$(30,207);CHR$(128);
30 PRINT CHR$(207);STRING$(30," ");CHR$(207);
40 PRINT CHR$(207);STRING$(30," ");CHR$(207);
50 PRINT CHR$(207);STRING$(30," ");CHR$(207);
60 PRINT CHR$(207);STRING$(30," ");CHR$(207);
70 PRINT CHR$(207);STRING$(30," ");CHR$(207);
80 PRINT CHR$(207);STRING$(30," ");CHR$(207);
90 PRINT CHR$(207);STRING$(30," ");CHR$(207);
100 PRINT CHR$(207);STRING$(30," ");CHR$(207);
110 PRINT CHR$(207);STRING$(30," ");CHR$(207);
120 PRINT CHR$(207);STRING$(30," ");CHR$(207);
130 PRINT CHR$(207);STRING$(30," ");CHR$(207);
140 PRINT CHR$(207);STRING$(30," ");CHR$(207);
150 PRINT CHR$(207);STRING$(30," ");CHR$(207);
160 PRINT CHR$(207);STRING$(30," ");CHR$(207);
170 PRINT CHR$(128);STRING$(30,207);
180 GOTO 180

When I time those two, there is a teeny speed improvement in the second version (around 1/60th of a second). Is that enough to justify the overhead of the extra memory needed? Probably not in this example, but it might be if it was in code that was redrawing a screen for a game or something.

Side Note: Of course, we could speed this up by pre-generating the strings and the PRINTing them. That would avoid all the parsing of CHR$() and the numbers and building temporary strings over and over and over.

10 CLS 0
20 PRINT CHR$(128);STRING$(30,207);CHR$(128);
25 L$=CHR$(207)+STRING$(30," ")+CHR$(207);
30 FOR I=1 TO 14
40 PRINT L$;
50 NEXT
60 PRINT CHR$(128);STRING$(30,207);
70 GOTO 70

That is about 40% faster than the original, and it could be made even faster, but that’s not the point currently.

Inlining

Another way to speed things up is eliminate as many GOTOs and GOSUBs as you can. Every GO has to search either forward, line by line, looking for the target line (if going to a line after the current one), or start at the very first line and search forward (if starting at an earlier line).

A 10,000 line program will find it quite slow to be on line 10000 and type GOTO 9999. Likewise, line 1 saying “GOTO 10000” will be quite slow.

If there is enough memory, inlining subroutine code will speed things up every time the code is used. Consider this subroutine from my old *ALL RAM* BBS:

15 BR$="*==============*==============*"
...
25 A$="Welcome To *ALL RAM* BBS!":GOSUB1055
...
50 'New User
55 A$="Password Application Form":GOSUB1055
...
100 'Main Menu
105 A$="*ALL RAM* BBS Master Menu":GOSUB1055
...
150 'Call Sysop
155 A$="Calling the Sysop":GOSUB1055
...
200 'Goodbye
205 A$="Thank you for calling":GOSUB1055
...
250 'Userlog
255 A$="List of Users":GOSUB1055
...
1050 'Function Border
1055 CLS:PRINTBR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$:RETURN

The routine at line 1050 takes a string in A$. It will clear the screen, print a border string, print the A$ centered, then print the border string again and return.

The time it takes to run this code should be the same no matter where it appears in the program, but the time it takes to get to this code will vary depending on where it is called from. If you were calling it from line 1040, it would be quite fast. If you were calling it from line 20, it has to scan forward through every line from 20-1050 to find it, which would be slower. At least, because it is a GOSUB routine, it will return quickly. (That’s a big advantage of using GOSUB over GOTO.)

This routine could be inlined in to the code, and each use would be slightly faster.

15 BR$="*==============*==============*"
...
25 A$="Welcome To *ALL RAM* BBS!"
26 CLS:PRINTBR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$
...
50 'New User
55 A$="Password Application Form"
56 CLS:PRINTBR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$
...
100 'Main Menu
105 A$="*ALL RAM* BBS Master Menu"
106 CLS:PRINTBR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$
...
150 'Call Sysop
155 A$="Calling the Sysop"
156 CLS:PRINTBR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$
...
200 'Goodbye
205 A$="Thank you for calling"
206 CLS:PRINTBR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$
...
250 'Userlog
255 A$="List of Users"
256 CLS:PRINTBR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$
...

Again, this is a pretty poor example, but if this routine was something that required speed (a game, animation, etc.) every little bit would help.

Other time-saving techniques

I have covered many other optimizations to code, such as using “&H” hex numbers rather than decimal and using “.” instead of 0, and all of these things can combine to make a dramatically faster program — at the expense of code size. (A=9 takes three bytes, while A=&H9 is faster but takes four bytes.)

But if we were back in 1980 and trying to program on a 4K CoCo, perhaps we would have more things to worry about than speed.

Size matters

On that 1980 4K CoCo, a “PRINT MEM” on startup shows 2343 bytes free for a program. If you didn’t plan to use any strings or string functions, a CLEAR 0 would give back 200 bytes for a total of 2534 bytes free. You probably won’t be doing any loop unrolling or inlining in this environment.

Instead, focusing on the smallest way to do something makes more sense.

Subroutine everything!

If any bit of code is used more than once, it may make sense to make it a subroutine as long as the overhead of having to add “GOSUB xxx” and “RETURN” do not take more than the duplicate code would.

10 X=0:Y=0:P=(Y*32)+X:PRINT@P,"HELLO"

Above, some X and Y coorindates (representing the CoCo’s 32×16 text screen) are converted to a PRINT@ screen position (0-511). This is the type of code that might appear many places where something is printed at the screen. This makes it a good candidate for being a subroutine:

10 X=0:Y=0:GOSUB 1000:PRINT@P,"HELLO"
...
1000 P=(Y*32)+X:RETURN

Each time “P=(Y*32)+X” is used, that takes 10 bytes. The overhead for “GOSUB 1000” is 8 bytes, then the subroutine itself adds 5 bytes for the line overhead and 2 bytes for “:RETURN”. That’s 15 bytes extra we just added, but if it were used more than a few times, it starts saving memory. (See note below about line numbers and how you can make this save even more.)

And, if the routine was using X/Y to a P position to print something, you might as well put that in the subroutine as well and eliminate the P variable completely:

10 X=0:Y=0:A$="HELLO":GOSUB 1000
...
1000 PRINT@(Y*32)+X,A$:RETURN

With just a bit of reworking code, many bytes can be saved.

But that’s not the best example of saving bytes, because “GOSUB 1000” takes up a lot of memory. Let’s look at how to make it take up less.

Small line numbers save bytes

When a program is tokenized (keywords like PRINT replaced with one and two byte tokens representing the command), the line number is changed in to a two-byte representation of the line. It doesn’t matter if you are at line 5 or line 55000, the line number will take up two bytes of the tokenized line.

But, when you have a line number in the line itself, such as as the target of GOTO, GOSUB if an IF/THEN, those digits will be stored in full just as you typed them:

10 GOTO 10
20 GOTO 100
30 GOTO 1000
40 GOTO 10000

Above, line 20 will take up one more byte than line 10, because “100” takes one more byte than “10”. Because of this, using shorter line numbers will save space. Instead of this:

10 PRINT "ENTER YOUR NAME";:GOSUB 10000
20 PRINT "PHONE NUMBER";:GOSUB 10000
30 PRINT "FAVORITE COLOR";:GOSUB 10000
...
1000 INPUT A$:RETURN

…you could move the subroutine to the top and use single digit line numbers like this:

0 GOTO 10
1 INPUT A$:RETURN
...
10 PRINT "ENTER YOUR NAME";:GOSUB 1
20 PRINT "PHONE NUMBER";:GOSUB 1
30 PRINT "FAVORITE COLOR";:GOSUB 1
...

In this example, you have immediately added extra overhead by having line 0, but as long as you save enough to offset that, the program will be smaller. In this example, the three “GOSUB 10000” were turned in to “GOSUB 1” saving four bytes every time they were used.

As a bonus, having the subroutines at the start of the program will make them faster, since every GOSUB will be to a higher number, and BASIC will just start at the first line and scan forward, finding the subroutine much quicker. Smaller and faster!

And, of course, don’t forget to number your program by 1 — or, if using Extended Color BASIC with the RENUM command, you can do this:

RENUM newline, startline, increment
Renumbers a program.
newline is the first new renumbered line. If you omit newline, BASIC uses 10.
startline is where the renumbering starts. If you omit start/ine, BASIC renumbers the entire program.
increment is the increment between each renumberedline. If you omit increment, BASIC uses 10.
Note: RENUM does not rearrange the order of lines.

– Getting Started With Extended Color BASIC (1984 edition), page 58
RENUM 0,0,1

That will renumber the program to start at line 0, beginning at line line 0, and incrementing by 1s. If you started with:

100 PRINT
200 PRINT
300 PRINT
400 PRINT

…a RENUM 0,0,1 will give you:

0 PRINT
1 PRINT
2 PRINT
3 PRINT

You may save a few bytes every time a GOTO/GOSUB or IF/THEN is used.

Side Note: I believe it was Alex “Basic Utils” Evans who recently pointed out to me that GOTO and GOSUB each used two bytes. They are actually three token words — GO, TO and SUB. You can even write them separated with a space like “GO TO 100” or “GO SUB 100“. I hadn’t seen that since I was first learning BASIC back in 1982 or so, in some books a schoolmate was loaning me.

Avoid gratuitous use of lines

Every line in Color BASIC has five bytes of overhead. If you typed in a line that was just:

10 A

…you would see memory goes down by six bytes. Five bytes for the line overhead, and one byte for the “A”.

Side Note: Those five bytes are a 2-byte address of the next line, a 2-byte line number, and a trailing 0 terminator at the end of the line. In the above example, the bytes would look like “[xx][xx][yy][yy][A][0]” for that line.

Because of this, writing things out on separate lines consumes more memory, and is also slower since BASIC has more line number data to parse (included when doing a GOTO/GOSUB).

10 PRINT
20 PRINT
30 PRINT
40 PRINT
50 PRINT

…will be 15 bytes larger than doing:

10 PRINT:PRINT:PRINT:PRINT:PRINT

The first example would be tokenized as:

[xx][xx][yy][yy][print_token][0] <- 6 bytes
[xx][xx][yy][yy][print_token][0] <- 6 bytes
[xx][xx][yy][yy][print_token][0] <- 6 bytes
[xx][xx][yy][yy][print_token][0] <- 6 bytes
[xx][xx][yy][yy][print_token][0] <- 6 bytes (30 bytes total)

…versus this for the second example:

[xx][xx][yy][yy][print_token][:][print_token][:][print_token][:][print_token][:][print_token][:][0] <- 15 bytes total

In Color BASIC, the input buffer will allow you to type up to 249 characters for one line, so you can really pack a lot of commands together and save space (and speed).

Side Note: In Extended BASIC, the EDIT command can be used to get a few extra characters on a line. If you type a long line that features BASIC keywords up to the max of 249 characters, then press ENTER, the line gets tokenized and words that took up five characters like “PRINT” get turned in to one or two byte tokens. This can free up enough space to allow doing an EDIT on the line, then an Xtend to go to the end of the line, and type a few more characters. “When every last byte matters…”

Other space-saving techniques

  • REM – If space matters, leave out any REMarks in the code as they both take up extra space and slow the program down. I would sometimes comment my programs pretty heavily, then save the commented version out, and then delete the remmarks and save out the smaller version.
  • Spaces – Removing any unnecessary spaces (“GO TO 1000” becomes “GOTO1000”) helps.
  • Semicolons – A semi-colon is only required at the end of a PRINT line if you want to avoid having the carriage return printed, or when separating variables that BASIC cannot tell apart form keywords. For example, “PRINT A;B;C” is obviously needed because “PRINT ABC” would be a different variable. But, the semicolon is assumed between items you can print such as “PRINT VAL(A$);B$;CHR$(128)” which works just fine as “PRINT VAL(A$)B$CHR$(128)”. Heck, even when printing string variables like “PRINT A$B$C$D$” the semicolons are not needed. BUT, you still need it if BASIC can’t tell. If you wanted to print A$, B (number) and C$, you would need “PRINT A$B;C$” since BASIC needs to know where the B non-string variable ends.
  • Trailing Quotes – If a line ends in a quote, the quote can be left off. This goes for things like PRINT”SOMETHING” or even A$=”SOMETHING”. BASIC will figure it out when it gets to the end of the line.
  • Parenthesis – There are times when parenthesis are required to make math work (order of operations), but sometimes it is just done to make the code easier to read (like spaces and splitting up one instruction per line). In my X/Y example, I used “(Y*32)+X” but the parens don’t matter. The multiplication will happen first, then the addition, so “Y*32+X” will save two bytes. TEST FIRST! Some things look like they should work, but will require parens. (“1*256+1 – 1*256+1” feels like it should be zero, but it will be 2. For this you do need “(1*256+1)-(1*256+1)”.

And, of course, figuring out how to reduce code and combine things is always part of the process.

See also “Carl England’s CRUNCH program” which will do those steps for you.

There are many other tips and tricks for optimizing BASIC for speed or size, but hopefully these examples get you started in experimenting.

Until next time…

Canon Camera Connect iOS app v3.0 storing same GPS location in every photo.

I am posting this in case someone else with the same issue is searching for a solution.

The Canon Camera Connect app can record GPS locations, then later upload them to a supported Canon camera where they get embedded in the photo files. Unfortunately, the V3 app has a bug. It appears to store only ONE GPS location in every file taken — the location where the phone was when you click “Start recording” in the app. That GPS location and timestamp will be in every photo (I took 508 photos today and all confirm this; and I had a similar issue last month with it doing it every day for six days).

This is with the iOS version and a Canon G5X phone. The v2 had its own problems, but at least worked well when it worked. I have been using it since 2017.

If you find this post and have this issue, leave a comment with your details.

CoCo Disk BASIC disk structure – part 2

See also: part 1 or part 2.

In the first installment, we began exploring the anatomy of a Disk BASIC disk and how it is made up of tracks and sectors. We also took a peek at the file allocation table (FAT) and a simple program was shared that would count the number of empty granules on the disk. It did this by reading the file allocations able sector (track 17, sector 2) and looking at the first 68 bytes (which represent the status of the 68 available granules). If a byte was 255, the granule is free.

But what if it isn’t free?

File allocation table (FAT) revisited

In that case, the number will represent one of two things:

  1. If the granule is used, but the file is larger and continues on to another granule, the value will be the granule number for the next granule of the file. It’s a linked list!
  2. If the granule is used, but the file does not continue to another granule, the top two bits will be set (11xxxxxx, hex value &HC0 or decimal 192) and the remaining five bits will indicate how many sectors of that granule are part of the file.

If each granule is 2304 (and it is), and a file is 6000 bytes, it is going to need three granules (2304 *3 = 6912) to store that file. That would be two full granules (4608 bytes) and then the remaining 1292 bytes (6000-4608=1392) in the third granule. 1392 bytes needs 6 sectors (6*256 = 1536) to fit the remaining data.

But, since files are not always exactly multiples of 256-bytes, there is one more bit of information that tells how many bytes in the final sector are used by the file. That value is part of the directory entry in bytes 14-15:

Since the full file size is not part of the directory entry, we will need to scan the File Allocation Table and do some calculations. Here are what the 68 bytes of the FAT can be:

RS-DOS FAT

To calculate the size of a file, we need to do these steps:

  1. Get the file’s directory entry (32 bytes), specifically byte 13 (the number of the first granule in the file) and bytes 14-15 (the number of bytes used in the last sector of the file).
  2. Read the FAT byte that corresponds to the start granule of the file, then…
    • If the value is 0-67, that is the number of the next granule used by the file. Add the size of the granule (2304 bytes) and get the next granule value.
    • If the value has high two bits set (11000000), the remaining value will be how many sectors of that granule are used by the file. Since this is the last sector, add the “number of bytes used in the last sector” from the directory entry (bytes 14-15) then the number of sectors minus 1 multiplied by the size of a sector (256).

“And it’s just that easy!”

So let’s try it… Here is a more “complete” DIR program, though this time instead of doing less, it does more by showing the size of the file in bytes, rather than how many granules it takes up on disk, and by showing file types in a more verbose/descriptive way.

To speed it up (even though it is still slow), it will load the FAT entries in to an array, along with the directory entries. This makes calculating the size easier since everything is now a variable in an array rather than having to read and parse bytes from a disk sector.

10 ' FILEINFO.BAS
20 '
30 ' 0.0 2023-01-25 ALLENH
40 ' 0.1 2023-01-26 ADD DR
50 ' 0.2 2023-01-27 MORE COMMENTS
60 '
70 ' E$(0-1) - SECTOR HALVES
80 ' FT$ - FILE TYPE STRINGS
90 '
100 CLEAR 1500:DIM E$(1),FT$(3)
110 FT$(0)="BPRG":FT$(1)="BDAT":FT$(2)="M/L ":FT$(3)="TEXT "
120 '
130 ' DIR HOLDS UP TO 72 ENTRIES
140 '
150 ' NM$ - NAME
160 ' EX$ - EXTENSION
170 ' FT - FILE TYPE (0-3)
180 ' AF - ASCII FLAG (0/255)
190 ' FG - FIRST GRANULE #
200 ' BU - BYTES USED IN LAST SECTOR
210 ' SZ - FILE SIZE
220 '
230 DIM NM$(71),EX$(71),FT(71),AF(71),FG(71),BU(71),SZ(71)
240 '
250 INPUT "DRIVE";DR
260 '
270 ' FILE ALLOCATION TABLE
280 ' 68 GRANULE ENTRIES
290 '
300 DIM FA(67)
310 DSKI$ DR,17,2,G$,Z$:Z$=""
320 FOR G=0 TO 67
330 FA(G)=ASC(MID$(G$,G+1,1))
340 NEXT
350 '
360 ' READ DIRECTORY
370 '
380 DE=0
390 FOR S=3 TO 11
400 DSKI$ DR,17,S,E$(0),E$(1)
410 '
420 ' PART OF SECTOR
430 '
440 FOR P=0 TO 1
450 '
460 ' ENTRY WITHIN SECTOR PART
470 '
480 FOR E=0 TO 3
490 '
500 ' DIR ENTRY IS 32 BYTES
510 '
520 E$=MID$(E$(P),E*32+1,32)
530 '
540 ' NAME IS FIRST 8 BYTES
550 '
560 NM$(DE)=LEFT$(E$,8)
570 '
580 ' EXTENSION IS BYTES 9-11
590 '
600 EX$(DE)=MID$(E$,9,3)
610 '
620 ' FILE TYPE IS BYTE 12
630 '
640 FT(DE)=ASC(MID$(E$,12,1))
650 '
660 ' ASCII FLAG IS BYTE 13
670 '
680 AF(DE)=ASC(MID$(E$,13,1))
690 '
700 ' FIRST GRANUAL IS BYTE 14
710 '
720 FG(DE)=ASC(MID$(E$,14,1))
730 '
740 ' BYTES USED IN LAST SECTOR
750 ' ARE IN BYTES 15-16
760 '
770 BU(DE)=ASC(MID$(E$,15,1))*256+ASC(MID$(E$,16,1))
780 '
790 ' IF FIRST BYTE IS 255, END
800 ' OF USED DIR ENTRIES
810 '
820 IF LEFT$(NM$(DE),1)=CHR$(255) THEN 1390
830 '
840 ' IF FIRST BYTE IS 0, FILE
850 ' WAS DELETED
860 '
870 IF LEFT$(NM$(DE),1)=CHR$(0) THEN 1370
880 '
890 ' SHOW DIRECTORY ENTRY
900 '
910 PRINT NM$(DE);TAB(9);EX$(DE);"  ";FT$(FT(DE));" ";
920 IF AF(DE)=0 THEN PRINT"ASC"; ELSE PRINT "BIN";
930 '
940 ' CALCULATE FILE SIZE
950 ' SZ - TEMP SIZE
960 ' GN - TEMP GRANULE NUM
970 ' SG - SECTORS IN LAST GRAN
980 '
990 SZ=0:GN=FG(DE):SG=0
1000 '
1010 ' GET GRANULE VALUE
1020 ' GV - GRAN VALUE
1030 '
1040 GV=FA(GN)
1050 '
1060 ' IF TOP TWO BITS SET (C0
1070 ' OR GREATER), IT IS THE
1080 ' LAST GRANULE OF THE FILE
1090 ' SG - SECTORS IN GRANULE
1100 '
1110 IF GV>=&HC0 THEN SG=(GV AND &H1F):GOTO 1280
1120 '
1130 ' ELSE, MORE GRANS
1140 ' ADD GRANULE SIZE
1150 '
1160 SZ=SZ+2304
1170 '
1180 ' MOVE ON TO NEXT GRANULE
1190 '
1200 GN=GV
1210 GOTO 1040
1220 '
1230 ' DONE WITH GRANS
1240 ' CALCULATE SIZE
1250 '
1260 ' FOR EMPTY FILES
1270 '
1280 IF SG>0 THEN SG=SG-1
1290 '
1300 ' FILE SIZE IS SZ PLUS
1310 ' 256 BYTES PER SECTOR
1320 ' IN LAST GRAN PLUS
1330 ' NUM BYTES IN LAST SECT
1340 '
1350 SZ(DE)=SZ+(SG*256)+BU(DE)
1360 PRINT " ";SZ(DE)
1370 DE=DE+1
1380 NEXT:NEXT:NEXT
1390 END
1400 ' SUBETHASOFTWARE.COM

It looks like this:

And that, I suppose, is about as much disk talk as I can handle for the moment. Let me know in the comments if I missed anything else important.

Until then…