Category Archives: Color BASIC

CoCo bouncing BASIC ball, part 4

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

Since I like to jump around, let’s do just that.

CoCo cross development revisited

Awhile ago, I posted about Microsoft Visual Studio Code and CoCo cross development. I just remembered this was a thing, so I have been doing some experiments tonight using the Xroar emulator and Visual Studio Code to quickly type up BASIC code and then load it into the emulator via an ASCII file (simulating a cassette tape).

My process is this:

  1. With the Color BASIC extension for Visual Studio Code installed, I type up my BASIC program and save it out to a file called “ASCII.BAS”. (The filename doesn’t matter.)
  2. In Xroar, I select Load (from the cassette menu, or Apple-L on my Mac) and browse to this ascii.bas file I just saved.
  3. In Xroar, I type CLOAD and watch it quickly load in my text file as if it was loaded as an ASCII basic program from tape.
  4. I type RUN and see if it worked…
Visual Studio Code with the COLOR BASIC extension, loading in BASIC into the Xroar emulator.

This let’s me write code quickly on my Mac, and then test it out on the emulated CoCo without too much effort.

With that out of the way, let’s return to discussing this bouncing ball project…

Discussing the ball project

My earlier experiments show that the fastest way to print a block of characters on the screen is to calculate the position and then use PRINT@. For example, here is a 10 x 7 block of text that sorta looks like a ball. With variable P being the top left corner to PRINT@ to, I just add offset values to get to each line. I use hex values because that’s faster than using decimal:

100 REM BALL
110 PRINT@P+&H00,"  XXXXXX  ";
120 PRINT@P+&H20," X      X ";
130 PRINT@P+&H40,"X        X";
140 PRINT@P+&H60,"X        X";
150 PRINT@P+&H80,"X        X";
160 PRINT@P+&HA0," X      X ";
170 PRINT@P+&HC0,"  XXXXXX  ";

I can adjust the location of P and have this print the ball anywhere I want on the screen. But, since it’s a ball, it doesn’t really make sense for me to print those empty corners, so I removed them and adjusted the offsets:

100 REM BALL
110 PRINT@P+&H02,  "XXXXXX";
120 PRINT@P+&H21, "X       X";
130 PRINT@P+&H40,"X         X";
140 PRINT@P+&H60,"X         X";
150 PRINT@P+&H80,"X         X";
160 PRINT@P+&HA1, "X      X";
170 PRINT@P+&HC2,  "XXXXXX";

Just so I could see the ball better, I added spaces before the string quotes in lines 110, 120, 160 and 170. To make it faster, I’d remove those, and put all these PRINTs on one line (if it fits). Every little bit helps, but we’ll optimize for speed later.

Now, Jim Gerrie’s demo had different frames of animation which he did using an array of strings. But, printing arrays is slower (since it has to look up the values each time). I decided I’d try raw PRINTs and make each “frame” of the ball be a subroutine. It takes time to GOSUB to that routine, but it will RETURN quickly.

I could then use a variable to represent which frame to print, and use ON/GOSUB to get to it (at the overhead of teaching forward in the program to find that line number).

40 ON F GOSUB 100,200,300,400,500,600,700

We’d need to benchmark to see if searching the array is faster than searching line numbers. (Since each array string would have to be looked up, versus one search for a line number, I expect the GOSUB approach will be faster unless the program is huge and it has to search through tons of lines.)

Now I can do my X and Y movement calculating, conversion that to a PRINT@ location, and then GOSUB to the appropriate frame routine to display it.

To erase the ball, I could just clear the entire screen (CLS), or I could make a subroutine that just PRINTs over the old ball:

1000 REM ERASE
1100 PRINT@P+&H02,  "      ";
1200 PRINT@P+&H21, "        ";
1300 PRINT@P+&H40,"          ";
1400 PRINT@P+&H60,"          ";
1500 PRINT@P+&H80,"          ";
1600 PRINT@P+&HA1, "        ";
1700 PRINT@P+&HC2,  "      ";
1800 RETURN

This should greatly reduce the amount of flicker.

Let’s see what the program looks like now:

10 CLS
20 X=0:Y=0:XM=1:YM=1:F=1:FM=1
30 GOSUB 1000:P=X+Y*&H20
40 ON F GOSUB 100,200,300,400,500,600,700
50 X=X+XM:IF X<&H1 OR X>&H15 THEN XM=-XM:FM=-FM
60 Y=Y+YM:IF Y<&H1 OR Y>&H8 THEN YM=-YM
70 F=F+FM:IF F>7 THEN F=1 ELSE IF F<1 THEN F=7
80 GOTO 30

In line 10, I clear the screen. Right now, I’m just using text on the green screen, but ultimately I’ll want to clear the screen to some background color, and “erase” by printing that color over the old ball.

Line 20 initializes the variables I will be using:

  • X – X position of the top left corner of the ball.
  • Y – Y position of the top left corner of the ball.
  • XM – value to add to X for the next X movement (1 to move to the right, -1 to move to the left).
  • YM – value to add to Y for the next Y movement (1 to move down, -1 to move up).
  • F – frame of the ball to display. Since ON GOTO/GOSUB uses base-1 values, frames will be 1-x.
  • FM – value to add to F to get to the next frame. When moving left to right, I’ll add 1 and increment the frame. When the ball bounces off the right side of the screen, I’ll start adding -1 and reverse the animation.

Line 30 erases the ball at the current position. This doesn’t make sense the first time we RUN, but it will have something to erase every time after that. We also calculate the PRINT@ P position from the X and Y values.

Line 40 does the ON GOSUB to the routine to print whatever frame we are supposed to display. If F is 1, it GOSUBs to 100. If F is 4, it GOSUBs to 400.

Line 50 adds the XM value to X, giving us our next X position. It then checks to see if X has gone too far left, or too far right, and reverses the XM value if so.

Line 60 is the same as above, but for the Y value.

Line 70 is similar, but either increments or decrements the frame, then checks to see if it needs to wrap around to the frame at the other side.

After this, we just need the routines that print the ball frames and erase the ball frame:

100 REM FRAME 1
110 PRINT@P+&H02,  "XXXXXX";
120 PRINT@P+&H21, "X      X";
130 PRINT@P+&H40,"X        X";
140 PRINT@P+&H60,"X        X";
150 PRINT@P+&H80,"X        X";
160 PRINT@P+&HA1, "X      X";
170 PRINT@P+&HC2,  "XXXXXX";
180 RETURN

200 REM FRAME 2
210 PRINT@P+&H02,  "XXXXXX";
220 PRINT@P+&H21, "XX     X";
230 PRINT@P+&H40,"XX       X";
240 PRINT@P+&H60,"XX       X";
250 PRINT@P+&H80,"XX       X";
260 PRINT@P+&HA1, "XX     X";
270 PRINT@P+&HC2,  "XXXXXX";
280 RETURN

300 REM FRAME 3
310 PRINT@P+&H02,  "XXXXXX";
320 PRINT@P+&H21, "XXX    X";
330 PRINT@P+&H40,"XXX      X";
340 PRINT@P+&H60,"XXX      X";
350 PRINT@P+&H80,"XXX      X";
360 PRINT@P+&HA1, "XXX    X";
370 PRINT@P+&HC2,  "XXXXXX";
380 RETURN

400 REM FRAME 4
410 PRINT@P+&H02,  "XXXXXX";
420 PRINT@P+&H21, "X XX   X";
430 PRINT@P+&H40,"X XXX    X";
440 PRINT@P+&H60,"X XXX    X";
450 PRINT@P+&H80,"X XXX    X";
460 PRINT@P+&HA1, "X XX   X";
470 PRINT@P+&HC2,  "XXXXXX";
480 RETURN

500 REM FRAME 5
510 PRINT@P+&H02,  "XXXXXX";
520 PRINT@P+&H21, "X  XX  X";
530 PRINT@P+&H40,"X  XXXX  X";
540 PRINT@P+&H60,"X  XXXX  X";
550 PRINT@P+&H80,"X  XXXX  X";
560 PRINT@P+&HA1, "X  XX  X";
570 PRINT@P+&HC2,  "XXXXXX";
580 RETURN

600 REM FRAME 6
610 PRINT@P+&H02,  "XXXXXX";
620 PRINT@P+&H21, "X   XX X";
630 PRINT@P+&H40,"X    XXX X";
640 PRINT@P+&H60,"X    XXX X";
650 PRINT@P+&H80,"X    XXX X";
660 PRINT@P+&HA1, "X   XX X";
670 PRINT@P+&HC2,  "XXXXXX";
680 RETURN

700 REM FRAME 7
710 PRINT@P+&H02,  "XXXXXX";
720 PRINT@P+&H21, "X    XXX";
730 PRINT@P+&H40,"X      XXX";
740 PRINT@P+&H60,"X      XXX";
750 PRINT@P+&H80,"X      XXX";
760 PRINT@P+&HA1, "X    XXX";
770 PRINT@P+&HC2,  "XXXXXX";
780 RETURN

1000 REM ERASE
1100 PRINT@P+&H02,  "      ";
1200 PRINT@P+&H21, "        ";
1300 PRINT@P+&H40,"          ";
1400 PRINT@P+&H60,"          ";
1500 PRINT@P+&H80,"          ";
1600 PRINT@P+&HA1, "        ";
1700 PRINT@P+&HC2,  "      ";
1800 RETURN

(After I typed this, I realize I need a FRAME 8, but I’ll fix that later.)

You can see my simple attempt at making the ball “spin” in action here:

With this proof-of-concept done, I can now get back to trying to make it get done faster by optimizing the BASIC code for speed.

To be continued…

CoCo bouncing BASIC ball, part 3

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

Math versus DATA: FIGHT!

In the previous post, I showed the simple way I would bounce a ball around the screen by using X and Y coordinates and converting them to PRINT@ screen position, and then using an X Movement and Y Movement variable to control where the ball went next.

But, since this is a demo, we don’t actually need to calculate anything realtime. We could have all the positions stored in DATA statements, and just READ them as the program ran. This would also allow fancier movement patterns, such as bouncing with gravity.

Yes, it’s cheating. But is it faster? Let’s find out. Here is some code that repeatedly reads a location from DATA statements then displays an “*” at that position.

6 P=0
30 READP:IFP=&HFFF THENRESTORE:GOTO30
31 PRINT@P,"*";
1000 DATA&H1,&H2,&H3,&H4,&H5,&H6,&H7,&H8,&HFFFF

The above code is inserted in to my BENCH.BAS program.

In line 30, we read a value from the DATA statements. If that value is &HFFFF (65535), RESTORE is used to rewind the READ so the next time it happens it looks for the first DATA statement. The GOTO causes it to try the READ again (thus, restarting with the first bit of data when it runs out of DATA).

Line 31 just prints our ball at whatever position was in the DATA statement.

Pretty simple.

Running this shows that reading 8 data values then rewinding over and over again (1000 times total) takes about 768. That’s a huge improvement of calculating the X and Y each time (1842). Thus, we should be able to pre-calculate the ball positions and go from there.

Writing code that generates code

We can write some code that writes an ASCII (text) file to disk (or tape) that contains numbers lines of BASIC which could be loaded (or MERGED from disk) later. Let’s start by just PRINTing out the lines we’d want to generate:

5 X=0:Y=0:XM=1:YM=1
10 LN=1000
20 LN$=STR$(LN)+" DATA"
30 P=X+Y*32
40 IF LEN(LN$)<240 THEN LN$=LN$+"&H"+HEX$(P)
50 IF LEN(LN$)<239 THEN LN$=LN$+"," ELSE PRINTLN$:LN=LN+10:GOTO 20
60 X=X+XM:IFX<1ORX>30THENXM=-XM
70 Y=Y+YM:IFY<1ORY>14THENYM=-YM
80 GOTO30

When you run that, it starts printing out lines of DATA statements containing hex values:

1000 DATA&H0,&H21,&H42,&H63,&H84,&HA5,&HC6,
   &HE7,&H108,&H129,&H14A,&H16B,&H18C,&H1AD,
   &H1CE,&H1EF,&H1D0,&H1B1,&H192,&H173,&H154,
   &H135,&H116,&HF7,&HD8,&HB9,&H9A,&H7B,&H5C,
   &H3D,&H1E,&H3F,&H5E,&H7D,&H9C,&HBB,&HDA,
   &HF9,&H118,&H137,&H156,&H175,&H194

1010 DATA&H194,&H1B3,&H1D2,&H1F1,&H1D0,&H1AF,
   &H18E,&H16D,&H14C,&H12B,&H10A,&HE9,&HC8,
   &HA7,&H86,&H65,&H44,&H23,&H2,&H21,&H40,
   &H61,&H82,&HA3,&HC4,&HE5,&H106,&H127,
   &H148,&H169,&H18A,&H1AB,&H1CC,&H1ED,
   &H1CE,&H1AF,&H190,&H171,&H152,&H133,&H114

All we’d have to do is figure out how many positions we want, and then print these lines to an ASCII file on disk or tape instead of to the screen. For instance, if we start at position 0, maybe we generate values until we bounce back to position 0. (To make things easier, I’m going to start at 1,1 and end when it gets back to 0).

0 CLEAR1000
5 X=1:Y=1:XM=1:YM=1
10 LN=1000
15 OPEN "O",#1,"DATA.ASC"
20 LN$=STR$(LN)+" DATA"
30 P=X+Y*32
31 PRINTP;
35 IF P=0 THEN CLOSE#1:END
40 IF LEN(LN$)<240 THEN LN$=LN$+"&H"+HEX$(P)
50 IF LEN(LN$)<239 THEN LN$=LN$+"," ELSE PRINT#1,LN$:LN=LN+10:GOTO 20 60 X=X+XM:IFX<1ORX>30THENXM=-XM
70 Y=Y+YM:IFY<1ORY>14THENYM=-YM
80 GOTO30

This program will produce a text file called DATA.ASC containing all the positions the ball will be in until it loops back to the top left corner of the screen. This can then be loaded (LOAD”DATA.ASC”) into BASIC. (Disk BASIC allows MERGE”DATA.ASC” to merge those lines in with whatever BASIC program is already there, just as if they were typed in by hand.)

With this pre-calculated data, all we have to do is just read a position, then display the ball there.

10 CLS
20 READP:IFP=&HFFFF THENRESTORE:GOTO20
30 PRINT@P,"*";:GOTO20
5000 DATA&HFFFF

Note I needed to add the final &HFFFF, but I should have made the DATA generator program add that before closing the file.

Those lines and all the generated DATA statements make a blazing fast bouncing ball with no math involved – just the time it takes to READ a value from DATA statements.

With this proof-of-concept, the next step will be seeing if this can speed up printing a large block of text for a huge ball.

To be continued…

CoCo bouncing BASIC ball, part 2

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

Math is hard, but some is easier.

Since it seems we might be needing math to move the ball around, let’s revisit some benchmarks on math to see which is faster. Using my BENCH.BAS program:

0 REM BENCH.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

…let’s declare a temporary variable, and then see how fast it is to add different values.

6 Z=0
30 Z=Z+1

Adding decimal 1 gives us about 280.

Changing that to hex 1 (Z=Z+&H1) is basically the same.

BUT, adding 1 would just be moving on byte to the right. If we were moving down and to the right, that would be adding 33.

Z=Z+33 produces 355. It’s having to do much more work to convert two decimal digits.

Changing that to Z=Z+&H21 results in 278. It seems adding two hex digits is faster than adding one decimal digit :-)

Obviously, any math we want to do should be done using hex. But what math do we need?

Let’s start with a simple example that tries to “bounce” a single character around the screen.

Bounce this

As a kid, I would do this with an X and Y, which I would then convert to a screen position like this:

PRINT@X+Y*32,"*";

I would then use variables for “X Movement” (XM) and “Y Movement” (YM) and add them to X and Y each time:

X=X+XM:IF X<1 OR X>30 THEN XM=-XM
Y=Y+YM:IF Y<1 OR Y>14 THEN YM=-YM

There are problems with this approach (like, when it hits the bottom right of the screen, it scrolls up, and I’m also not erasing the “*”), but it’s good enough for a benchmark test:

6 X=0:Y=0:XM=1:YM=1
30 PRINT@X+Y*32,"*";
31 X=X+XM:IFX<1ORX>30THENXM=-XM
32 Y=Y+YM:IFY<1ORY>14THENYM=-YM

This produces 2047. If we change all the values to hex:

6 X=0:Y=0:XM=1:YM=1
30 PRINT@X+Y*&H20,"*";
31 X=X+XM:IFX<&H1ORX>&H1E THENXM=-XM
32 Y=Y+YM:IFY<&H1ORY>&HE THENYM=-YM

Note I had to add spaces after the &H1E and &HE since BASIC can’t tell that’s the end of a value before parsing the next keyword (“THEN”).

This one change improves the speed to 1842. A better way would be to eliminate the X and Y conversion completely, and just track one position (say, the top left corner). But without an X and Y, how do you know when you’ve hit the edge?

Since this is a demo, perhaps we don’t have to.

To be continued…

CoCo Cross Developing with MS Visual Studio Code

I just wanted to post a note here about Microsoft’s Visual Studio Code cross-platform editor. It is available for Windows, Mac and Linux, though I have so-far only used it on Mac and Windows:

https://code.visualstudio.com

It supports extensions and there are several CoCo-related ones available:

  • CoCoTools by Jason Pittman. This provides some level on integration with the Windows-based VCC CoCo emulator, as well as lwasm assembler, Toolshed CoCo disk tools, and cmoc C compiler. I just found this today and have not used it. It says it can do things like RENUM BASIC files, which could be useful.
  • Color BASIC by Tandy. By Tandy, you say? Yes, Tandy UK! This color-codes BASIC program listings. This is what I have been using for my Mac-based BASIC editing.
  • 6809 and 6309 Assembly by Tandy. This one color-codes assembly source.
  • 6×09 Assembly by Blair Leduc. This one also color-codes assembly source.

Visual Studio Code offers some IDE features, allowing extensions to launch compilers/assemblers and even run them. It looks like CoCoTools does this, so I look forward to trying it out soon.

Until next time…

CoCo bouncing BASIC ball, part 1

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

The prolific Jim Gerrie recently posted a video of an MC-10 bouncing ball demo he wrote in BASIC:

Jim Gerrie’s BASIC bouncing ball for the MC-10.

Thanks to Jim, the MC-10 joins a long list of 80’s home computers that thought bouncing a ball around the screen was a good demo.

Seeing his demo inspired me to revisit my Benchmarking BASIC series and see if we can expand on this.

I begin with the excellent (and free) Xroar Emulator. For those that want to play along, there is now a web browser version you can try without installing anything, or you can try the original JS Mocha emulator. Both should be more than enough for this experiment.

First, I load up my BENCH.BAS framework so I can do some speed tests. It looks like this:

0 REM BENCH.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

The above sample code will run four tests (0 to 3) and each time it will do “something” 1001 times (0 to 1000) and report the results in CoCo TIMER values (where each number represents 1/60th of a second, give or take). The benchmark code would go between lines 20 and 70.

Let’s see how big of an object we want to display. Using Jim’s video as a reference (where he shows some of the BASIC source code), it looks like 8 characters wide by 9 characters tall:

Based on him using substrings going up to 6, it looks like he may have 7 frames of animation (0-6). Impressive. But let’s just start with displaying 8×9 characters in various ways to see how fast we can do it.

The fastest way in BASIC is to use a PRINT@ statement, so my first test is to see how long it takes to print these characters 1001 times. I will hard-code the location for each line starting with 0 (the top left of the screen). It looks like this:

30 PRINT@0,"12345678";:
   PRINT@32,"12345678";:
   PRINT@64,"12345678";:
   PRINT@96,"12345678";:
   PRINT@128,"12345678";:
   PRINT@160,"12345678";:
   PRINT@192,"12345678";:
   PRINT@224,"12345678";:
   PRINT@256,"12345678";

Running that in the benchmark program results in 3986.

Using decimal values is slow, so next I changed them to hex:

30 PRINT@&H0,"12345678";:
   PRINT@&H20,"12345678";:
   PRINT@&H40,"12345678";:
   PRINT@&H60,"12345678";:
   PRINT@&H80,"12345678";:
   PRINT@&HA0,"12345678";:
   PRINT@&HC0,"12345678";:
   PRINT@&HE0,"12345678";:
   PRINT@&H100,"12345678";

Parsing a hex value is much faster, so this results in 3018. That’s about 25% faster.

And, using variables is even faster, since no parsing is required. A quick test that sets variables I-Q to each value, then does a PRINT@I through PRINT@Q produces 2940. Not a huge gain, but every little bit helps. That’s probably the fastest one can print something like this in BASIC.

The problem with using multiple variables is you have to update each one every time the object moves. If you just use one variable, and do math for all the other lines, you are doing the same math for each line that would have been done for the variable.

At this point, we need to see which way of doing math is faster. Or, perhaps, maybe we can do it without using math. . .

To be continued…

BASIC REM memory savings

Over in the CoCo Facebook group, Diego Barzio just posted a note about discovering some BASIC program line comments that had left out the REM keyword at the start. Because those lines were never the target of a GOTO, there was no error. This had the side effect of saving a byte (or two, since most do “REM ” at the start) for comment lines.

This made me think about what was going on. BASIC will allow you to type in anything, and will try to parse it (to tokenize the line) and produce an error (?SN ERROR, etc.) if there is a problem. But, when you are typing in a line, this BASIC doesn’t check anything until that line is actually ran in the program. (Other versions of BASIC will check when you hit enter. Didn’t the Apple 2 do that?)

REM

In an earlier article, I mentioned that “REM” tokenized into one byte, and the apostrophe (‘) turned into two bytes because it was “:REM(space)” allowing you do do something like this:

10 REM This is a comment.
20 'This is a comment

Since most folks type “REM(space)”, using the apostrophe without a space after it takes the same room. But if you include the space:

10 REM This is a comment
20 ' This is a comment

…that would make line 10 take one byte less (REM + space is two bytes, versus ‘ + space which is three bytes).

Now, if you do not include REM or apostrophe, you can save a byte or two for each comment. If you program has 50 lines of comments, that’s 50-100 bytes.

BASIC Keywords

However, this made me realize that leaving out the REM would cause it to try to tokenize the line, and turn any BASIC keyword into a token, saving even more memory. If your comment included words like PRINT, OR, ELSE, etc., you’d save even more room!

Leaving our REM can save memory? Thanks, Diego Barizo!

As you can see, because this comment uses the word PRINT (five characters), the version without the REM appears to save six bytes — it saves the apostrophe (two bytes, or would save “REM(space)” for two bytes) plus tokenizes the PRINT keyword down from five bytes to one (saving four more bytes).

Interesting BASIC interpreter abuse, isn’t it? As long as you never run this line, you might save memory by leaving our REM (depending on if you use any keywords). I could imaging comments saying “SHOW THE USER NAME” changed to “PRINT THE USR NAME” to tokenize two words (PRINT and USR).

You would still need REM at the start of the code for any initial comments, and during the code, but for subroutines you could do this:

10 REM THIS IS MY PROGRAM
20 A$="HELLO, WORLD!":GOSUB 1010
999 END
1000 PRINT THE MESSAGE
1010 PRINT A$:RETURN

The GOSUB jumps to 1010 and can find it, and since the program would END before reaching 1000, that would work.

With great power comes great responsibility. Use this wisely :) and thanks, Diego, for noticing this.

Until next time…

Microsoft Visual Studio Code (PC/Mac/Linux) for CoCo cross development.

Microsoft Visual Studio Code for Mac (left) with extension to color code BASIC code for easy loading into Xroar emulator (right).

As mentioned elsewhere, there are some CoCo-related extensions available for the Microsoft Visual Studio Code editor. I did not realize that MS released this editor for Mac and Linux as well as Windows.

Here is the free editor:

https://code.visualstudio.com

Once installed, you can download (from within the editor) extensions.

Here are two extensions by Tandy UK — one for COLOUR BASIC and the other for 6309/6809 assembly:

https://marketplace.visualstudio.com/publishers/Tandy

There is another 6809 assembly extension available:

https://marketplace.visualstudio.com/items…

It colorizes files ending in .a or .asm. 

I don’t see many details about the Tandy UK ones, but I am indeed getting colorized BASIC keywords when I have a .bas file.

Introducing the Sir Sound CoCo Sound Card

NEW “PRODUCT” ANNOUNCEMENT

The team that brought you* the CoCoPilot DriveWire Server is proud to announce their latest innovation:

“Sir Sound”

Sir Sound is a solid-state multi-voice audio synthesizer that operates over a serial transport mechanism**. It provides arcade-quality*** sound with up to three independent tonal voices plus one white noise channel all in an external module that doesn’t require voiding your warranty to install. In fact, you won’t even need tools!

Pricing is to be announced but hopefully it will be around $50. Or maybe $30. Or cheaper. Or less if you build it yourself. Heck, we’ll probably just make kit versions available since we don’t really like to solder.

Sir Sound Configurations

  • Turnkey – This is a “plug and go” version where you just plug it in and go. No special drivers are needed, as they are already built in to both BASIC and OS-9.****
  • BYOE – The bring-your-own-everything edition is shipped as a set of simple instructions containing a parts list and how to run wires between the parts.
  • Custom – Also planned to be available are various custom configurations, like what color of case it comes in.

Pricing

We estimate the thing is gonna cost us, like, ten or so bucks to make using off-the-shelf parts ordered in small quantities from China. But, to make it a product, we really should have an integrated circuit board and a case made, which will run the costs up dramatically. Rest assured, we’ll pass those unsavings along to you!

Availability

The first prototype is in the process of being tested. Quit rushing us. We’ll let you know when it’s done.

Specs

Basically it’s a Texas Instruments SN76489 sound chip hooked to a tiny Arduino micro-controller with a TTL-to-RS232 adapter. Here’s the prototype John Strong of StrongWare sent me:

SN76849 sound chip hooked to an Arduino Nano on a neat 3-D printed platform from StrongWare.

You kinda have to use some micro-controller since the sound chip turns on and starts making sound. Something has to issue the “shut up” instruction to it. If you just had hardware to translate a serial byte in to the command, and made the CoCo do all the work, the CoCo would have to load and run a program to shut the thing up every time you powered up. Fortunately, a custom-built Arduino that handles this can be done for like $5. There are cheaper PIC chips that could do it for less.

Then, you add a MAX232 type chip that goes from the TTL signal levels of the Arduino to RS232 signal levels, or using one of these $3 (or less) boards that’s already wired:

TTL-to-RS232 adapter.

Lastly, add a CoCo serial cable (4-pin DIN to DB9), and you are set.

Prototype “Sir Sound” sound module for the CoCo (or anything with a serial port, actually).

A small program on the Arduino will monitor the serial port for bytes and then relay them to the sound chip.

By doing some POKEs in BASIC to set the baud rate, you could make music by doing things like this:

REM PLAY MIDDLE C
PRINT #-2,CHR$(&H8E);CHR$(&H1D);CHR$(&H90);

FOR A=1 TO 1000:NEXT A

REM VOLUME OFF
PRINT #-2,CHR$(&H9F);

The notes always play, so you shut them off by setting volume off. There are different channel values for each of the four channels.

I envision having a “raw” mode where the device just translates the bytes from serial to the sound chip, and a “smart” mode where you could use an API and just send note values (like 1-88 of a piano keyboard, or MIDI note values).

“Smart” mode could simplify the control so it might look like this:

REM ALL DATA: PLAY CHANNEL 0, NOTE 10, AT VOLUME 15
PRINT #-2,CHR$(&H00);CHR$(&HA);CHR$(&HF);

REM NOTE ONLY: PLAY CHANNEL 0, NOTE 10
PRINT #-2,CHR$(&H01);CHR$(&HA);

REM NOTE ONLY: PLAY CHANNEL 1, NOTE 10
PRINT #-2,CHR$(&H11);CHR$(&HA);

REM VOLUME ONLY: CHANNEL 0, VOLUME 5
PRINT #-2,CHR$(&H20);CHR$(&H5);

And, I could also add a “super smart” mode where it could parse PLAY command-style strings, then spool them in the background while you do other things:

REM PLAY COMMAND, CHANNEL 0
PRINT #-2,CHR$(&H30);"CDEFGAB";

And, a “super super smart” mode could let it store string sequences, and play them by triggering with a simple byte:

REM STORE NOTE SEQUENCE 0
PRINT #-2,CHR$(&H40);"CCDCECFECCDCECFE";CHR$(0);

REM PLAY NOTE SEQUENCE 0
PRINT #-2,CHR$(&H50);

REM PLAY NOTE SEQUENCE 0 FIVE TIMES
PRINT #-2,CHR$(&H55);

…or whatever. You could sequence them together, like MIDI sequencers do, and have complex patterns that could play in the background while the program does other things.

There are lots of possibilities. We could even see about using the Carrier Detect line as a way to tell if the sound card was still playing something (rather than needing routines to read data back from the device, which would be doable but not from BASIC without assembly language code).

If this “sounds” fun to you, leave a comment…

Until then…


Notes:

* If you call making a blog post “bringing it” to you.

** It plugs in to the Serial I/O port. “Sir” sounds like “Ser”, get it? Marketing thought SerSound wasn’t friendly enough.

*** This part is true. The same sound hardware is used in the arcade mega-hits Congo Bongo and Mr. Do, among others.

**** PRINT#-2, yo.

Building a Better BASIC Input, revisited

Well, here we go again with a few more tidbits from comments to a previous installment

Lowercase to Uppercase

John Klassek once again provides some interesting tips. He looked at my Building a Better BASIC Input article example:

30 LN=INSTR(“YyNn”,A$):IF LN=0 THEN PRINT”YES OR NO ONLY!”:GOTO 20
40 ON LN GOTO 100,100,200,200

…and suggested this:

40 ON (LN+1)/2 GOTO 100,200

…and noted:

…and you don’t have to keep the line numbers twice in ON…GOTO.

Well that makes sense. The extra bytes taken to do the math would be smaller than all the doubled line numbers. But does doing the math take more time than parsing through the extra line numbers? Let’s fine out:

5 DIM TE,TM,B,A,TT
10 FORA=0TO4:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 LN=INSTR("YyNn",A$):IF LN=0 THEN PRINT"YES OR NO ONLY!":GOTO 20
40 ON LN GOTO 100,100,200,200
70 NEXT:TE=TIMER-TM
80 TT=TT+TE:PRINTA,TE
90 NEXT:PRINTTT/A:END
100 GOTO 70
200 GOTO 70

This produces 866.

John’s suggestion:

5 DIM TE,TM,B,A,TT
10 FORA=0TO4:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 LN=INSTR("YyNn",A$):IF LN=0 THEN PRINT"YES OR NO ONLY!":GOTO 20
40 ON (LN+1)/2 GOTO 100,200
70 NEXT:TE=TIMER-TM
80 TT=TT+TE:PRINTA,TE
90 NEXT:PRINTTT/A:END
100 GOTO 70
200 GOTO 70

…jumps up to 1214! So, smaller code, but at a speed penalty. (See: Size versus Speed.) Brute force is often faster.

However, I also discussed converting characters to UPPERCASE so you only had to compare uppercase. That came with quite the speed penalty:

0 DIM A$,C:A$="*"
5 DIM TE,TM,B,A,TT
10 FORA=0TO4:TIMER=0:TM=TIMER
20 FORB=0TO1000
40 C=ASC(A$):IF C>=97 AND C<=122 THEN A$=CHR$(C-32)
70 NEXT:TE=TIMER-TM
80 TT=TT+TE:PRINTA,TE
90 NEXT:PRINTTT/A:END

That benchmark was 960.

John then presented a much faster approach by using strings and the INSTR command:

0 DIM A$,C,Z:A$="*"
1 AL$="aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
5 DIM TE,TM,B,A,TT
10 FORA=0TO4:TIMER=0:TM=TIMER
20 FORB=0TO1000
40 Z=INSTR(AL$,A$): IF Z>1 THEN C=64+Z/2 ELSE C=ASC(A$)
70 NEXT:TE=TIMER-TM
80 TT=TT+TE:PRINTA,TE
90 NEXT:PRINTTT/A:END

That drops it a bit lower to 920. For a custom input routine, every bit helps make it keep up with faster typing. Nicely done! (He also notes that this assumes the character string is non-empty, which we’d ensure is the case if using an INKEY$.) And, remember that we could speed that up even more by changing the decimal numbers to HEX.

He also suggests using AND instead of my “C-32” subtraction:

In case of a conversion with A$=CHR$(ASC(A$)-32) this could be a slightly faster: A$=CHR$(ASC(A$)AND M) but only with a predefinition of M=223 (with constant 223 it would be slower!) which masks bit 5 (value 32) in the character code representation.

Challenge accepted:

0 DIM A$,C,M:A$="*"
1 M=&HDF '110111115 DIM TE,TM,B,A,TT
10 FORA=0TO4:TIMER=0:TM=TIMER
20 FORB=0TO1000
40 C=ASC(A$):IF C>=97 AND C<=122 THEN A$=CHR$(C AND M)
70 NEXT:TE=TIMER-TM
80 TT=TT+TE:PRINTA,TE
90 NEXT:PRINTTT/A:END

This returns 963 while my original using subtraction returned 960. It looks like AND is slower. HOWEVER, if you know the input is already ONLY “a-z”, you can just do the AND without the check. So, there is a use-case if you were scanning for “yYnN” and just AND the result and compare only uppercase….

It’s fun to think outside the box.

Any other suggestions?

Until then…

Color BASIC String Theory, part 2

See also: part 1 and part 2.

I had no intention of turning this in to another rambling multi-part article series, so maybe I can wrap this up in just two posts…

But first, in a comment to the first part, William “Lost Wizard” Astle said:

String manipulation is, in fact, entirely predictable, and logical. I think I’ll do an article on the internals of string evaluation and manipulation in Color Basic. It’s actually fairly straight forward, but the whys and hows of it get a bit complex. – William Astle

His article explains how the string handling really works (unlike my random speculation), and includes some interesting tips about how to make things faster by allocating extra string space if memory allows. Check it out here:

…and then read the rest of my speculative fiction about how strings work.

Speculative Fiction About How Strings Work

My original purpose was to talk about making sure you CLEAR enough string memory to keep your program from crashing if a user types in a big string or maxes out all the variables. This will become important when I get to designing the new version of the ALLRAM BBS I plan to write.

Today, let’s look at why this mattered when I created the original version back in 1983.

When I first created ALLRAM, I decided I would make the message board work on 64-character lines. That would fit nicely on two lines of the CoCo’s 32-column screen, or one line on the 64-column TRS-80 Model I and III screens. Isn’t it weird thinking back to the early days before 80 columns became standard?

On a cassette based CoCo, after a PCLEAR 0 (no graphics pages), I could get 31015 bytes of memory to use for storing the userlog and message base.

Somehow, I decided that I would let each message be up to 10 lines long (640 bytes max), and I used an extra line to store the TO, FROM and SUBJECT of the message. From looking at the old source code, I see that usernames were limited to 20 characters, and the subject was 18 for some reason. They were combined using backslash delimiters like this:

TO\FROM\SUBJECT

At max size (20+1+20+1+18) that is 60 bytes. Ah, now I understand why the subject must have been 18. This makes each message take up to 700 bytes total (640+60).

I used a two-dimensional array to hold the 11 lines for each of the 20 messages:

DIM MS$(19,10)

Twenty messages at 700 bytes each is 14000 bytes. I had to make sure I CLEARed that much plus anything else needed for operation.

I also decided I would store the userlog as an array of strings that contained the username, password and access level, separated by a delimiter:

USERNAME\PASSWORD\LEVEL

A username could be up to 20 characters long and a password 8 characters. The level was supposed to be from 0-9, so it could be 1 character. Thus, the maximum userlog string could be 31 bytes (20+1+8+1+1).

I decided I would allow up to 201 users. I suppose I expected user 0 to be the SysOp (system operator) account.

DIMNM$(200)

This meant the userlog could take up to 6231 bytes (201*31).

Now the RAM storage used by the BBS was 20231 bytes, assuming every byte of every user and message entry was full.

I knew I would need some extra storage for temporary strings and such, so I just rounded up:

CLEAR 21000

And that’s how ALLRAM came to exist on a 32K Radio Shack Color Computer. (I had 64K, but Color BASIC did not make use of anything past 32K, and I didn’t know assembly language yet which would have been needed to access the upper 64K. See this article for details.)

You may be able to see that this was a “safe” approach, but probably had a TON of memory that just wasn’t being used. Consider a message like this:

----------------------------------------------------------------
TO: ALLEN HUFFMAN
FR: SYSOP
SB: COOL!

THIS SIMULATES WHAT A MESSAGE MIGHT HAVE LOOKED LIKE ON THE 1983
*ALLRAM* BBS, USING UP TO TEN TEXT LINES OF 64 CHARACTERS. IF I
DIDN'T USE IT ALL, THE REMAINING MEMORY WAS JUST WASTED.

-<>- ALLEN HUFFMAN -<>-
----------------------------------------------------------------

The array that contained this message would look like this:

            ----------------------------------------------------------------
MS$(0,0) = "ALLEN HUFFMAN\SYSOP\COOL!"
MS$(0,1) = "THIS SIMULATES WHAT A MESSAGE MIGHT HAVE LOOKED LIKE ON THE 1983"
MS$(0,2) = "*ALLRAM* BBS, USING UP TO TEN TEXT LINES OF 64 CHARACTERS. IF I"
MS$(0,3) = "DIDN'T USE IT ALL, THE REMAINING MEMORY WAS JUST WASTED."
MS$(0,4) = ""
MS$(0,5) = "-<>- ALLEN HUFFMAN -<>-"
MS$(0,6) = ""
MS$(0,7) = ""
MS$(0,8) = ""
MS$(0,9) = ""
MS$(0,10)= ""

The row of dashes (“—“) represents the 64 character line size. As you can see, this small message is wasting much space because of unused lines, and even leftover space on the lines that are used.

If I were going to redesign this today (which I am), I have several ideas that would probably make it far more efficient, allowing for much larger message bases using the same memory (or, worst case, the same size if every message was maxed out).

Any thoughts?

I’ll share mine in a future installment.