How much faster is HEX versus decimal on the CoCo?

This is a topic I have mentioned in earlier articles about speeding up BASIC. I recently found one of my test programs and thought I’d update it a bit and share it.

A quick recap…

In BASIC, numbers in the program are stored as the characters. If you have “A=123” that “123” appears in the BASIC code as those characters. When the program RUNs, basic has to convert that series of characters to a number.

It takes more work to parse a standard base-10 (decimal, digits 0-9) set of characters to a number than it does to parse a base-16 (HEX, digits 0-F). To demonstrate, I wrote a very simple parser. It takes a string and converts it to a number, manually. (BASIC already has a VAL keyword that does this, but I wanted to show how it works.) Note this does not look for negative (“-“) or decimals or scientific notation – just a basic number like “12345”.

Decimal String to Number

10 ' STRTONUM.BAS
20 LINE INPUT "TYPE A NUMBER: ";NM$
30 GOSUB 60
40 PRINT "THE VALUE IS :";NM
50 END
60 ' CONVERT NM$ TO NM NUMBER
70 NM=0:ZM=1
80 ' SCAN FROM RIGHT TO LEFT
90 FOR Z=LEN(NM$) TO 1 STEP -1
100 ' GET CHARACTER VALUE
110 ZC=ASC(MID$(NM$,Z,1))
120 ' EXIT IF NOT 0-9
130 IF ZC>=ASC("0") THEN IF ZC<=ASC("9") THEN 160
140 PRINT "NOT A NUMBER : ";NM$
150 RETURN
160 ' SUBTRACT ASCII "0" AND MATH
170 NM=NM+(ZC-ASC("0"))*ZM
175 PRINT ZC-ASC("0"); "*";ZM;"=";NM
180 ' 0, 10, 100, 1000, ETC.
190 ZM=ZM*10
200 NEXT
210 RETURN

If you have a number like “12345”, you start at the right-most ASCII character (“5”) and convert that to a number. You multiply that number by a multiplier that starts out as 1. So, “5 * 1 = 5”.

Then you multiply the multiplier by 10, so 1 becomes 1. You move to the next digit, “4”, and multiply it by the new multiplier. So, “4*10 = 40”. That is added to the previous value, so “5 + 40 = 45”.

The multiplier is multiplied by 10, so 10 becomes 100. The next digit is a “3”, so that becomes “3 * 100 = 300”. It is added to the sum – “300 + 45 = 345”.

Repeating, the multiplier is multiplied by 10 making it 1000. The next digit is a “2”, so that becomes “2 * 1000 = 2000”. That is added making “2000 + 345 = 2345”. I think we see the pattern.

After the multiplier is multiplied by 10 again, making it 10000, we go through this again with the final digit, the “1”. That makes “1 * 10000 = 10000” and that is added making “10000 + 2345 = 12345”.

Whew!

HEX String to Value

But, with a hexadecimal value, the math gets much simpler since each character of a hex string represents a set of four bits in a byte. A similar converter is much smaller.

10 ' STRTONUM.BAS
20 LINE INPUT "TYPE A HEXNUM: ";NM$
30 GOSUB 60
40 PRINT "THE VALUE IS :";NM
50 END
60 ' CONVERT NM$ TO NM NUMBER
70 NM=0
80 ' SCAN FROM LEFT TO RIGHT
90 FOR Z=1 TO LEN(NM$)
100 ' GET CHARACTER VALUE
110 ZC=ASC(MID$(NM$,Z,1))
120 ' EXIT IF NOT 0-9 OR A-F
130 IF ZC>=ASC("0") THEN IF ZC<=ASC("9") THEN 200
140 IF ZC>=ASC("A") THEN IF ZC<=ASC("F") THEN 180
150 PRINT "NOT A HEXNUM : ";NM$
160 RETURN
170 ' CONVERT A-F TO 10-15
180 ZC=ZC-(ASC("A")-(ASC("9")+1))
190 ' NOW MAKE 0-15
200 ZC=ZC-ASC("0")
210 PRINT NM;"+";ZC;"=";
220 NM=NM+ZC
230 PRINT NM
240 IF Z<LEN(NM$) THEN NM=NM*16
250 NEXT
260 RETURN

In BASIC, once it sees some characters starting with “&H”, it gets the value of the next character. If it is A-F, it subtracts from the value so it looks like the ASCII values after “0” to “9”.

Then it subtracts the ASCII of “0” so the value becomes 0-15.

If there is another character, it shifts this value four places to the left (multiply by 16 in the code above) and it moves on to the next digit and repeats.

Bit shifts (multiplying by power of 2) are much quicker than multiplying. It makes the whole parsing routine much faster.

But how much faster?

I wrote this sample program which sits in a loop and sets a variable 10,000 times — first setting using a normal base-10 number (“A=1”), and then again using the HEX version (“A=&H1”). After each one it prints the TIMER value, and at the end of the line it prints how much faster (or slower) the HEX version was over decimal.

And as a bonus, at the end, it compares “A=.” (that is a fast shortcut for “A=0”) to “A=&H0” to show one case where there is something faster than HEX.

10 'DECVSHEX2.BAS
20 DIM A,I,TD,TH:MX=10000

30 PRINT "DO NOT PRESS KEYS WHILE RUNNING"
40 FOR I=1 TO 1000:NEXT I

50 PRINT "DEC TIME HEX TIME DIFF"

60 TIMER=0:FOR I=1 TO MX
70 A=0
80 NEXT:TD=TIMER:GOSUB 920
90 TIMER=0:FOR I=1 TO MX
100 A=&H0
110 NEXT:TH=TIMER:GOSUB 930

120 TIMER=0:FOR I=1 TO MX
130 A=1
140 NEXT:TD=TIMER:GOSUB 920
150 TIMER=0:FOR I=1 TO MX
160 A=&H1
170 NEXT:TH=TIMER:GOSUB 930

180 TIMER=0:FOR I=1 TO MX
190 A=2
200 NEXT:TD=TIMER:GOSUB 920
210 TIMER=0:FOR I=1 TO MX
220 A=&H2
230 NEXT:TH=TIMER:GOSUB 930

240 TIMER=0:FOR I=1 TO MX
250 A=3
260 NEXT:TD=TIMER:GOSUB 920
270 TIMER=0:FOR I=1 TO MX
280 A=&H3
290 NEXT:TH=TIMER:GOSUB 930

300 TIMER=0:FOR I=1 TO MX
310 A=4
320 NEXT:TD=TIMER:GOSUB 920
330 TIMER=0:FOR I=1 TO MX
340 A=&H4
350 NEXT:TH=TIMER:GOSUB 930

360 TIMER=0:FOR I=1 TO MX
370 A=5
380 NEXT:TD=TIMER:GOSUB 920
390 TIMER=0:FOR I=1 TO MX
400 A=&H5
410 NEXT:TH=TIMER:GOSUB 930

420 TIMER=0:FOR I=1 TO MX
430 A=6
440 NEXT:TD=TIMER:GOSUB 920
450 TIMER=0:FOR I=1 TO MX
460 A=&H6
470 NEXT:TH=TIMER:GOSUB 930

480 TIMER=0:FOR I=1 TO MX
490 A=7
500 NEXT:TD=TIMER:GOSUB 920
510 TIMER=0:FOR I=1 TO MX
520 A=&H7
530 NEXT:TH=TIMER:GOSUB 930

540 TIMER=0:FOR I=1 TO MX
550 A=8
560 NEXT:TD=TIMER:GOSUB 920
570 TIMER=0:FOR I=1 TO MX
580 A=&H8
590 NEXT:TH=TIMER:GOSUB 930

600 TIMER=0:FOR I=1 TO MX
610 A=9
620 NEXT:TD=TIMER:GOSUB 920
630 TIMER=0:FOR I=1 TO MX
640 A=&H9
650 NEXT:TH=TIMER:GOSUB 930

660 TIMER=0:FOR I=1 TO MX
670 A=10
680 NEXT:TD=TIMER:GOSUB 920
690 TIMER=0:FOR I=1 TO MX
700 A=&HA
710 NEXT:TH=TIMER:GOSUB 930

720 TIMER=0:FOR I=1 TO MX
730 A=11
740 NEXT:TD=TIMER:GOSUB 920
750 TIMER=0:FOR I=1 TO MX
760 A=&HB
770 NEXT:TH=TIMER:GOSUB 930

780 GOTO 850

790 TIMER=0:FOR I=1 TO MX
800 A=12
810 NEXT:TD=TIMER:GOSUB 920
820 TIMER=0:FOR I=1 TO MX
830 A=&HC
840 NEXT:TH=TIMER:GOSUB 930

850 TIMER=0:FOR I=1 TO MX
860 A=.
870 NEXT:TD=TIMER:PRINT " . ";:PRINT USING"#####";TD;:PRINT" ";
880 TIMER=0:FOR I=1 TO MX
890 A=&H0
900 NEXT:TH=TIMER:GOSUB 930

910 GOTO 910

920 PRINT USING"### ##### ";A;TD;:PRINT" ";:RETURN

930 PRINT "&H";HEX$(A);" ";:PRINT USING"##### ";TH;:PRINT USING "###.##%";((TD-TH)/TD)*100:RETURN

Running this program (eventually) displays:

So the answer is — for zero, 7% faster. For other single digit values, only slightly faster. For multiple digit values, about 30% faster. BUT, if using zero, using the period (“A=.”) will be about 16% faster than using HEX.

Thank you for coming to my talk.

Until next time…

1986 Doctor Who demo source found.

As I wrote about earlier, in 1986 I created a demo program that displays a Doctor Who logo (based on the one from the Tom Baker era of the show) and played a version of the theme song I created in Musica 2.

Doctor Who music demo.

You can download a disk image of this demo from the CoCo archive site:

https://colorcomputerarchive.com/repo/Disks/Demos/Dr.%20Who%20Demo%20%28Allen%20Huffman%29.zip

I created the music by ear (listening to a cassette tape recording I made from TV) in Musica 2:

Here is a video of the demo playing:

I remembered using a BASIC program to draw most of the logo, then using some graphics program to add the text. I have recently located the original BASIC program:

10 PMODE4,1:COLOR0,1:PCLS:SCREEN1,1
15 GOTO30
20 DRAW"BM128,20;M35,90;M128,160;M220,90;M128,20;BM128,30;M47,90;M128,150;M208,90;M128,30"
25 GOTO25
30 CIRCLE(128,135),100,,1,.66,.85:CIRCLE(128,135),80,,1,.66,.85:LINE(73,53)-(83,68),PSET:LINE(182,53)-(172,68),PSET:PAINT(128,36)
40 DRAW"BM65,70;M70,110;M80,110;M85,95;M90,110;M100,110;M105,70;M95,70;M93,95;M85,80;M78,95;M78,95;M75,70;M65,70":PAINT(66,71)
50 DRAW"BM110,70;M110,110;M120,110;M120,95;M140,95;M140,110;M150,110;M150,70;M140,70;M140,85;M120,85;M120,70;M110,70":PAINT(111,71)
60 CIRCLE(175,90),21:CIRCLE(175,90),12:PAINT(175,71)
70 GOTO20

It draws this:

Sometime, I’d like to try to create a more accurate version of that logo, and update the music to be more accurate as well.

Sometime.

DJI MIC 2 transmitter works with Insta360 X3 with no adapter needed.

In addition to the new DJI MIC 2 working with the native iPhone camera app, it can also be used directly with an Insta360 X3 360 camera. With the previous DJI MIC or something like the RODE microphone system, you needed to use the microphone receiver and an Insta360 microphone adapter and have it all connected to the camera with a cable.

The DJI MIC 2 adds Bluetooth and you can pair it directly with the X3 just by pairing it as if it were AirPods. (I suppose Insta360 needs to update the firmware to just say “Bluetooth” if it supports devices other than Apple.)

This means a $99 DJI MIC 2 transmitter is all you need for remote audio for an X3 (or iPhone, with an extra step to switch the phone over to using it). There is no need for the receiver and cables unless you are wanting to use more than one microphone at a time (as far as I know, you can only pair one Bluetooth microphone at a time with the phone or X3).

Unfortunately, the DJI firmware disables local backup recording when using Bluetooth, and also disables the new noise elimination feature. This means if anything screws up with the Bluetooth connection, you are out of luck. Pity. Backup recording is one of the features that makes the DJI MIC so useful. Maybe this is a firmware thing they can change in the future.

Until then…

Using the DJI MIC 2 with the iPhone native camera app

The DJI MIC 2 can pair to an iPhone via Bluetooth, but the built-in camera app (the “native camera app”) does not automatically use it. Various YouTubers and web pages and even ChatGPT say you have to use a third-party camera app that allows selecting the Bluetooth microphone.

But that is not true. You can run an app such a RODE Reporter, select the DJI MIC 2, then run the native camera app and it works fine.

Thank you, RichardTaylorTV, for not just echoing the same junk everyone else is doing.

RichardTaylorTV

Getting LN in BASIC on a machine that only has LOG

I need to know the real way to do this. BING AI and ChatGPT keep giving me different solutions, and only this last one works. Or seems to work.

I was amused that the BING AI suggested using DEF FN to create this function on a BASIC without LN.

DEF FNLN(x)
=LOG(x) / LOG(2.71828)

Changing the X to uppercase allows this to work on the CoCo. The results are close — are they close enough? Would more decimal places in the LOG value help?

Where are the math people?

PUT defined by ASCII strings

NOTE: Can anyone tell me how to get the blank lines out of the code listings? I used to be able to copy/paste from MS Visual Studio Code with no problem, but something has changed in WordPress. For the past few weeks, I have to paste in (it looks fine), then when I look at it, it has the line feeds. I then EDIT, and copy/paste it back in to Code (where I see the blank lines) then search/replace those blank lines, then copy/paste it back in to WordPress. That has been working fine, but now that does not work. Maybe it’s a different between PC Code and Mac Code (PC using CR/LF or something?). Anyone know?

Revisiting the PUT from DATA concept… For those who don’t want to figure out the binary values for the characters, here is a routine that lets you define them using strings:

'TANK UP
DATA " XX "
DATA " XX "
DATA "XX XX XX"
DATA "XXXXXXXX"
DATA "XXXXXXXX"
DATA "XXXXXXXX"
DATA "XXXXXXXX"
DATA "XX XX"

The routine reads these 8-character strings, then goes through them to build a value based on where the X is found.

Here is some code you can play with…

0 'PUTDATA.BAS
1 '2024-01-22
2 'SHOWS HOW TO DO IT USING
3 'STRINGS RATHER THAN #S
4 '

10 'TO SPEED UP STR-TO-BYTE
20 DIM BT(7):FOR BT=0 TO 7:BT(BT)=2^BT:NEXT
30 '
40 'VARIABLES USED IN THE
50 'POKE-TO-ARRAY ROUTINE MUST
60 'BE PRE-ALLOCATED OR THEY
70 'WILL CAUSE ARRAY MEM TO
80 'SHIFT WHEN IT GET USED.
90 '
100 DIM L,V,Z,Z$
110 '
120 'EACH ARRAY ENTRY CAN STORE
130 '5 BYTES. AN 8X8 SINGLE
140 'COLOR CHARACTER IS 8, SO
150 'WE NEED TWO ARRAY ENTRIES
160 '(10 BYTES) TO FIT THE 8
170 'BYTE CHARACTER.
180 '
190 DIM TU(1),TD(1),TL(1),TR(1):GOSUB 320

200 '
210 ' TEST PROGRAM
220 '
230 PMODE 4,1:PCLS 1:SCREEN 1,1
240 PUT (0,0)-(7,7),TU
250 PUT (16,0)-(16+7,7),TD
260 PUT (32,0)-(32+7,7),TL
270 PUT (48,0)-(48+7,7),TR
280 GOTO 280

290 '
300 'LOAD SPRITE CHARACTERS
310 '
320 PRINT "LOADING DATA";
330 L=VARPTR(TU(0)):GOSUB 390
340 L=VARPTR(TD(0)):GOSUB 390
350 L=VARPTR(TL(0)):GOSUB 390
360 L=VARPTR(TR(0)):GOSUB 390
370 RETURN

380 '
390 'READ DATA AND POKE AT L
400 '
410 PRINT
420 'READ STRINGS AND CONVERT
430 'TO BYTES.
440 FOR Z=0 TO 7:V=0
450 READ Z$:PRINT Z$,;
460 FOR BT=1 TO 8
470 'FASTER WAY
480 IF MID$(Z$,BT,1)="X" THEN V=V+BT(BT-1)
490 'SLOW WAY
500 'IF MID$(Z$,BT,1)="X" THEN V=V+2^(BT-1)
510 NEXT:PRINT INT(V);HEX$(V)
520 POKE L+Z,(NOT V)+256:NEXT:RETURN
530 NEXT:PRINT V;HEX$(V):NEXT
540 RETURN

550 '
560 '8X8 SPRITE CHARACTERS
570 '
580 'TANK UP
590 DATA " XX "
600 DATA " XX "
610 DATA "XX XX XX"
620 DATA "XXXXXXXX"
630 DATA "XXXXXXXX"
640 DATA "XXXXXXXX"
650 DATA "XXXXXXXX"
660 DATA "XX XX"

670 'TANK DOWN
680 DATA "XX XX"
690 DATA "XXXXXXXX"
700 DATA "XXXXXXXX"
710 DATA "XXXXXXXX"
720 DATA "XXXXXXXX"
730 DATA "XX XX XX"
740 DATA " XX "
750 DATA " XX "

760 'TANK LEFT
770 DATA " XXXXXX"
780 DATA " XXXXXX"
790 DATA " XXXX "
800 DATA "XXXXXXX "
810 DATA "XXXXXXX "
820 DATA " XXXX "
830 DATA " XXXXXX"
840 DATA " XXXXXX"

850 'TANK RIGHT
860 DATA "XXXXXX "
870 DATA "XXXXXX "
880 DATA " XXXX "
890 DATA " XXXXXXX"
900 DATA " XXXXXXX"
910 DATA " XXXX "
920 DATA "XXXXXX "
930 DATA "XXXXXX "

You can take this and change the characters real easily to play with it. Here is a smiling face instead of the UP TANK:

580 'SMILING FACE
590 DATA " XXXXXX "
600 DATA "X X"
610 DATA "X X X X"
620 DATA "X X"
630 DATA "X X X X"
640 DATA "X XX X"
650 DATA " X X "
660 DATA " XXXX "

Have fun… When I get time, I’ll try to post versions of these routines on my Github that handle different size objects as well as 4-color objects for us on PMODE 1 and 4 screens.

Tank 8×8 with no flicker, tracks and sound.

When I posted a video of my CoCo Factory TNT test, Chet Simpson suggested I could eliminated flicker by making a double-high sprite, with the top half blank. When I put the falling bomb, it would erase the position after it. L. Curtis Boyle suggested something similar to my tank game.

So I did it. It took a bit to figure out the logic. I have four 8×8 characters of the tank facing each direction, then I have two 16×8 (for left, and right) and two 8×16 (for up and down) versions.

Ericomont mentioned that the tank could leave tracks (I guess, since it seems like my black on white screen looks like snow). I added those too.

And then some sound. When the tank is not moving, the sound is slow — as if the tank is idling. When the taking is moving, it makes a faster sound.

I am having fun playing.

This version does to have the firing. I need to optimize this a bit and then add some bullets to it ;-)

Here is a video:

And here is the current code. There are some commented out lines I was using for debugging – like making a box around where the tank’s current position is (TX/TY) and where the old position was (OX/OY). That was massively helpful in me being able to see the logic to how I should draw and erase the tanks.

Now that I understand it, maybe I can do it better.

And this code can certainly be optimized and be made much faster.

Enjoy…

0 ' 
1 ' TANKPUT8X8-SOUND.BAS
2 ' ALLEN C. HUFFMAN
3 ' SUBETHASOFTWARE.COM
4 ' 2023-01-19
5 '

10 ' LOAD SPRITES
20 DIM TU(1),TD(1),TL(1),TR(1),UE(4),DE(4),LE(4),RE(4),Z,L,V:GOSUB 2000

30 ' ARROW KEYS
40 KY$=CHR$(94)+CHR$(10)+CHR$(8)+CHR$(9)

50 ' 256X192 BLACK AND WHITE
60 PMODE 4,1:PCLS 1:SCREEN 1,1

70 ' TANK POSITION AND DIR.
80 TX=8:TY=8:TD=4:OX=8:OY=8:Z=0

90 ' ERASE PREV POSITION
100 LINE(OX,OY)-(OX+7,OY+7),PSET,BF
110 ' PUT TANK ON SCREEN
130 PUT(TX,TY)-(TX+7,TY+7),TU:GOTO 180
140 PUT(TX,TY)-(TX+7,TY+7),TD:GOTO 180
150 PUT(TX,TY)-(TX+7,TY+7),TL:GOTO 180
160 PUT(TX,TY)-(TX+7,TY+7),TR:GOTO 180
165 ' PUT WITH BLANK NEXT TO IT
166 PUT(TX,TY)-(TX+7, TY+15),UE:GOTO 180
167 PUT(OX,OY)-(OX+7, OY+15),DE:GOTO 180
168 PUT(TX,TY)-(TX+15,TY+7),LE:GOTO 180
169 PUT(OX,OY)-(OX+15,OY+7),RE

170 ' READ KEYBOARD (WASD)
180 Z=0:POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
181 'LINE(TX-1,TY-1)-(TX+8,TY+8),PRESET,B
182 'LINE(OX-1,OY-1)-(OX+8,OY+8),PRESET
185 Z=Z-1:IF Z<0 THEN Z=20:EXEC 43345
190 A$=INKEY$:IF A$="" THEN 185
191 'LINE(TX-1,TY-1)-(TX+8,TY+8),PSET,B
192 'LINE(OX-1,OY-1)-(OX+8,OY+8),PSET
200 LN=INSTR(KY$,A$):IF LN=0 THEN 180
210 OX=TX:OY=TY
220 ON LN GOTO 240,270,300,330

230 ' UP
240 IF TD=1 THEN IF TY>0 THEN TY=TY-8:GOTO 166
250 TD=1:GOTO 130
260 ' DOWN
270 IF TD=2 THEN IF TY<184 THEN TY=TY+8:GOTO 167
280 TD=2:GOTO 140
290 ' LEFT
300 IF TD=3 THEN IF TX>0 THEN TX=TX-8:GOTO 168
310 TD=3:GOTO 150
320 ' RIGHT
330 IF TD=4 THEN IF TX<248 THEN TX=TX+8:GOTO 169
340 TD=4:GOTO 160

999 GOTO 999

2000 ' LOAD SPRITE CHARACTERS
2010 PRINT "LOADING DATA";
2030 L=VARPTR(TU(0)):GOSUB 3000
2040 L=VARPTR(TD(0)):GOSUB 3000
2050 L=VARPTR(TL(0)):GOSUB 3000
2060 L=VARPTR(TR(0)):GOSUB 3000
2170 ' 8X16 AND 16X8
2180 L=VARPTR(UE(0)):GOSUB 3025
2190 L=VARPTR(DE(0)):GOSUB 3025
2200 L=VARPTR(LE(0)):GOSUB 3025
2210 L=VARPTR(RE(0)):GOSUB 3025
2220 RETURN

3000 ' READ DATA AND POKE AT L
3010 PRINT ".";
3020 'FOR Z=L TO L+7:READ V:POKE Z,(NOT V)+256:NEXT:RETURN
3021 FOR Z=L TO L+7:READ V:POKE Z,(NOT V)+256:NEXT:RETURN
3025 PRINT "--";
3026 FOR Z=L TO L+15:READ V:POKE Z,(NOT V)+256:NEXT:RETURN
3030 ' 8X8 SPRITE CHARACTERS
3040 ' TANK UP
3050 DATA 24,24,219,255,255,255,255,195
3060 ' TANK DOWN
3070 DATA 195,255,255,255,255,219,24,24
3080 ' TANK LEFT
3090 DATA 63,63,30,254,254,30,63,63
3100 ' TANK RIGHT
3110 DATA 252,252,120,127,127,120,252,252

3120 ' TANK UP+EMPTY
3130 DATA 24,24,219,255,255,255,255,195
3131 DATA 0,195,0,0,0,195,0,0
3140 ' TANK EMPTY+DOWN
3150 DATA 0,0,195,0,0,0,195,0
3151 DATA 195,255,255,255,255,219,24,24
3160 ' TANK LEFT+EMPTY
3170 DATA 63,68, 63,68, 30,0, 254,0, 254,0, 30,0, 63,68, 63,68
3180 ' TANK EMPTY+RIGHT
3190 DATA 34,252, 34,252, 0,120, 0,127, 0,127, 0,120, 34,252, 34,252

Porting a BBC Micro galaxy program to the CoCo

Over in the Facebook CoCo group, Carlos Comacho shared a link to a cool online emulator for the BBC Micro. The link contains a BASIC program listing that drew a very cool galaxy graphic.

Here is the page:

https://bbcmic.ro/?t=9db8C

Here is the BBC Micro program:

10 MODE1
20 VDU5
30 FORG=RND(-64)TO400
40 t=RND(99):q=RND(99)
50 u=RND(1280):v=RND(1024)
60 A=RND(1)*3
70 R=90/(1+RND(200))
80 Q=1+R*(.5+RND(1)/2)
90 a=1+3*RND(1)^2
100 M=1
110 IFRND(9)<4Q=R:t=0:q=0:A=0:M=PI/3:a=1
120 C=(1+3*RND(1)^2)*R*R
130 FORi=0TOC
140 S=-LNRND(1)
150 T=i*M
160 U=S*R*SINT
170 V=S*Q*COST
180 T=S*A
190 X=U*COST+V*SINT
200 Y=V*COST-U*SINT
210 D=(X*X+Y*Y)/(R*R+Q*Q)
220 Z=99*((2.7^-D)+.1)
230 Z=Z*(RND(1)-.5)^3
240 y=Y*COSt+Z*SINt
250 Z=Z*COSt-Y*SINt
260 x=u+X*COSq+y*SINq
270 y=v-X*SINq+y*COSq
280 P=POINT(x,y)+a
290 IFP>3P=3
300 GCOL0,P
310 PLOT69,x,y
320 NEXT,
330 REM

I decided to try converting it to the CoCo. This led me to looking up some BBC Micro Commands to understand what they did. I found a few significant differences:

  • All variables need to be uppercase to work on the CoCo.
  • PI is not a constant on the CoCo, so it needs to be defined.
  • Line 10 – MODE1 sets a 320×256 4 color screen. On the CoCo 1/2, the 4-color screen has a resolution of 128×191.
  • Line 20 – VDU5 – Not used on the CoCo. It outputs a screen character of 5 for some reason.
  • Line 30 – RND(-64) would seed the random number generator, but on the BBC Micro it returns the -64. On the CoCo, RND(-64) looks like it does the same as RND(0) on the CoCo, returning a value from 0.0 to .9999. The FOR/NEXT will just have to do -64 for the start number.
  • Line 80, 90, 120, 140 and 230 – Their RND(1) returns a number from 0.0 to .99999. This will be changed to RND(0) on the CoCo.
  • Line 110 and 290 – The CoCo needs a THEN between IF X>4 THEN DO SOMETHING.
  • Line 160,170, 190, 200, 240, 250, 260 and 270 – SIN and COS need parens on the CoCo.
  • Line 280 – POINT looks like PPOINT on the CoCo.
  • Line 290 – If the program was written for a 4-color “MODE1” display, I assume their colors are 0-3. On the CoCo, they are 1-4, so that is a minor change.
  • Line 300 – GCOL0,P sets a color, so probably COLOR P.
  • Line 310 – PLOT69,X,Y becomes PSET(X,Y)
  • Line 320 – Their BASIC has an interesting shortcut for NEXT. After a FOR A you need a NEXT A, but you can also just say “NEXT” and it uses the most recent FOR. If you have nested loops, like FOR A :FOR B … you can end with “NEXT:NEXT” or “NEXT B,A”. It looks like their BASIC allows leaving out the variables, but using the comma. I tried this on CoCo, and it didn’t work. That would have been a fun discovery. That just means “NEXT,” becomes “NEXT:NEXT”.

When I did a quick port, I saw it passing negative values to PPOINT and PSET, so I had to add an extra check that skips plotting anything that would be off the screen (X <0 or >255, and Y<0 or >191).

My attempt at a CoCo 1/2 version looks like this:

0 'BBC MICRO - 320X256	4 COLOUR DISPLAY
1 POKE 65395,0
5 PI=3.141592653589793238
10 'MODE1
11 PMODE 4,1:PCLS:SCREEN 1,1:PMODE 3,1
20 'VDU5
30 'FORG=RND(-64)TO400
31 FORG=-64TO400
40 T=RND(99):Q=RND(99)
50 U=RND(1280):V=RND(1024)
60 A=RND(1)*3
70 R=90/(1+RND(200))
80 'Q=1+R*(.5+RND(1)/2)
81 Q=1+R*(.5+RND(0)/2)
90 'A=1+3*RND(1)^2
91 A=1+3*RND(0)^2
100 M=1
110 'IFRND(9)<4Q=R:T=0:Q=0:A=0:M=PI/3:A=1
111 IFRND(9)<4THENQ=R:T=0:Q=0:A=0:M=PI/3:A=1
120 'C=(1+3*RND(1)^2)*R*R
121 C=(1+3*RND(0)^2)*R*R
130 FORI=0TOC
140 'S=-LNRND(1)
141 S=-(LOG(RND(0)) / 0.4342944819)
150 T=I*M
160 'U=S*R*SINT
161 U=S*R*SIN(T)
170 'V=S*Q*COST
171 V=S*Q*COS(T)
180 T=S*A
190 'X=U*COST+V*SINT
191 X=U*COS(T)+V*SIN(T)
200 'Y=V*COST-U*SINT
201 Y=V*COS(T)-U*SIN(T)
210 D=(X*X+Y*Y)/(R*R+Q*Q)
220 Z=99*((2.7^-D)+.1)
230 'Z=Z*(RND(1)-.5)^3
231 Z=Z*(RND(0)-.5)^3
240 'Y=Y*COST+Z*SINT
241 Y=Y*COS(T)+Z*SIN(T)
250 'Z=Z*COST-Y*SINT
251 Z=Z*COS(T)-Y*SIN(T)
260 'X=U+X*COSQ+Y*SINQ
261 X=U+X*COS(Q)+Y*SIN(Q)
270 'Y=V-X*SINQ+Y*COSQ
271 Y=V-X*SIN(Q)+Y*COS(Q)
275 IF X<0 OR X>255 THEN 321
276 IF Y<0 OR Y>191 THEN 321
280 'P=POINT(X,Y)+A
281 P=PPOINT(X,Y)+A
290 'IFP>3P=3
291 IFP>3THENP=3
300 'GCOL0,P
301 COLORP
310 'PLOT69,X,Y
311 PSET(X,Y)
320 'NEXT,
321 NEXT:NEXT
330 REM
340 GOTO 340

Unfortunately, these changes were not enough.

Can you help?

My old (and new) VIC-20 stuff is now on Github

Preservation…

https://github.com/allenhuffman/VIC-20

And so is my *ALL RAM* BBS for the CoCo:

https://github.com/allenhuffman/ALL-RAM-BBS

And various BASIC things from other blog posts:

https://github.com/allenhuffman/BASIC

And a Lites Out game from an upcoming blog series:

https://github.com/allenhuffman/Lites-Out

There is even a VIC-20 port of that one:

https://github.com/allenhuffman/VIC-20/tree/main/my%20new%20programs/lites%20out

C and returning values quickly or safely. But not both.

WARNING: This article contains a C coding approach that many will find uncomfortable.

In my day job as a mild-mannered embedded C programmer, I am usually too busy maintaining what was created before me to be creating something new for others to maintain after me. There was that one time I had two weeks that were very different, and fun, since they were almost entirely spent “creating” versus “maintaining.”

Today’s quick C tidbit is about getting parameters back from a C function. In C, you only get one thing back — typically a variable type like an int or float or whatever:

int GetTheUltimateAnswer()
{
    return 42;
}

int answer = GetTheUltimateAnswer();
print ("The Ultimate Answer is %d\n", answer);

If you need more than one thing returned, it is common to pass in variables by reference (the address of, or pointer to, the variable in memory) and have the function modify that memory to update the variables:

void GetMinAndMax (int *min, int *max)
{
    *min = 0;
    *max = 100;
}

int min, max;
GetMinAndMax (&min, &max)
printf ("Min is %d and Max is %d\n", min, max);

The moment pointers come in to play, things get very dangerous. But fast.

When passing values in, they get copied in to a new variable:

int variable = 42;

printf ("variable = %d\n", variable);
Function (variable);
printf ("variable = %d\n", variable);

void Function (int x)
{
    x = x + 1;
}

Try it: https://onlinegdb.com/WC3ihCAuj

Above, Function() gets a new variable (called “x” in this case) with the value of the variable that was passed in to the call. The function is like Las Vegas. Anything that happens to that variable inside the function stays inside the function – the variable disappears at the end of the function, while the original variable remains as-was.

C++ changes this, I have learned, so you can pass in variables that can be modified, but I am not a C++ programmer so this post is only about old-skool C.

Pointing to a variable’s memory

By passing in the address of a variable, the function can go to that memory and modify the variable. It will be changed:

int variable = 42;

printf ("variable = %d\n", variable);
Function (&variable);
printf ("variable = %d\n", variable);

void Function (int *x)
{
    *x = *x + 1;
}

Try it: https://onlinegdb.com/Y2Z9WUvFG

Passing by value is slower, since a new variable has to be created. Passing by reference just passes an address and the code uses that address – no new variable is created.

But, using a reference for just for speed is dangerous because the function can modify the variable even if you didn’t want it to. Consider passing in a string buffer, which is a pointer to a series of character bytes:

void PrintError (char *message)
{
    print ("ERROR: %s\n", message);
}

PrintError ("Human Detected");

We do this all the time, but since PrintError() has access to the memory passed in, it could try to modify it. If we passed in a constant string like “Human Detected”, that string would typically be in program memory (though this is not true for Harvard Architecture systems like the PIC and Arduino). At best, an operating system with memory protection would trap that access with an exception and kill the program. At worst, the program would self-modify (which was the case when I learned this on OS-9/6809 back in the late 80s — no memory protection on my TRS-80 CoCo!).

void PrintError (char *message)
{
    message[0] = 42;
}

PrintError ("Human Detected");

Above would likely crash, though if the user had passed in the buffer holding a string, it would just be modified:

void PrintError (char *message)
{
    message[0] = 42;
}

char buffer[80];
strncpy (buffer, "Hello, world!", 80);
printf ("buffer: %s\n", buffer);
PrintError (buffer);
printf ("buffer: %s\n", buffer);

Try it: https://onlinegdb.com/L50JRWYj

And your point is?

My point is — there are certainly times when speed is the most important thing, and it outweighs the potential problems/crashes that could be caused by a bug with code using the pointer. Take for example anything that passes in a buffer:

void UppercaseString (char *buffer)
{
    for (int idx=0; idx<strlen(buffer); idx++)
    {
        buffer[i] = toupper(buffer[I])
    }
}

There are many bad things that could happen here. By using “strlen”, the buffer MUST be a string that has a NIL (‘\0’) byte at the end. This routine could end up trampling through memory uppercasing bytes that are beyond the caller’s string.

It is wise to always add another parameter that is the max size of the buffer:

void UppercaseString (char *buffer, int bufferSize)
{
    for (int idx=0; idx<bufferSize; idx++)
    {
        buffer[i] = toupper(buffer[I])
    }
}

That helps. But it is still up to the compiler to catch the wrong type of pointer being passed in.

int Number = 10;

UppercaseString (&Number, 100);

The compiler should not let you do that, but some may just issue a warning and build it anyway. (This is why I always try to have NO warnings in my code. The more warnings there are, the more likely you will start ignoring them.)

Try #1: Passing by Reference

Suppose we have a function that returns the date and time as individual values (year, month, day, hour, minute and second). Since we cannot get six values back from a function, we first try passing in six variables by reference and having the routine modify them:

void GetDateTime1 (int *year, int *month, int *day,
                   int *hour, int *minute, int *second)
{
    *year = 2023;
    *month = 8;
    *day = 19;
    *hour = 4;
    *minute = 20;
    *second = 0;
}

int year, month, day, hour, minute, second;
GetDateTime1 (&year, &month, &day, &hour, &minute, &second);
printf ("GetDateTime1: %d/%d/%d %02d:%02d:%02d\n",
        year, month, day, hour, minute, second);

That works fine … as long as you know the parameters are “ints” (whatever that is) and not shorts or longs or any other numeric type. This, for example, would be bad:

short year, month, day, hour, minute, second;

GetDateTime1 (&year, &month, &day, &hour, &minute, &second);

Above, we are passing in a short (let’s say that is a 16-bit variable on this system) in to a function that expects an int (let’s say that is a 32-bit signed variable on this system). The function would try to place 32-bits of information at the address of a 16-bit value.

Bad things, as they say, can happen.

Try #2: Passing a structure by reference

Passing in six variable pointers is more work than passing in one, so if we put the values in a structure we could pass in just the pointer to that structure. This has the benefit of making sure the structure is only loaded with values it can handle (unlike passing in an address of something that might be 8, 16, 32 or 64 bits).

typedef struct
{
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
} TimeStruct;

void GetDateTime2 (TimeStruct *timePtr)
{
    timePtr->year = 2023;
    timePtr->month = 8;
    timePtr->day = 19;
    timePtr->hour = 4;
    timePtr->minute = 20;
    timePtr->second = 0;   
}

TimeStruct time;
GetDateTime2 (&time);
printf ("GetDateTime2: %d/%d/%d %02d:%02d:%02d\n",
        time.year, time.month, time.day,
        time.hour, time.minute, time.second);

This should greatly reduce the potential problems since you only have one pointer to screw up, and if you get the type correct (a TimeStruct) the values it contains should be fine since the compiler takes care of trying to set a “uint8_t” to “65535” (a warning, hopefully, and storing 8-bits of that 16-bit value as a “loss of precision”).

Try #3: Returning the address of a static

An approach various standard C library functions take is having some fixed memory allocated inside the function as a static variable, and then returning a pointer to that memory. The user doesn’t make it and therefore isn’t passing in a pointer that could be wrong.

TimeStruct *GetDateTime3 (void)
{
    static TimeStruct s_time;
    
    s_time.year = 2023;
    s_time.month = 8;
    s_time.day = 19;
    s_time.hour = 4;
    s_time.minute = 20;
    s_time.second = 0;

    return &s_time;
}

TimeStruct *timePtr;
timePtr = GetDateTime3 ();  
printf ("GetDateTime3: %d/%d/%d %02d:%02d:%02d\n",
       timePtr->year, timePtr->month, timePtr->day,
       timePtr->hour, timePtr->minute, timePtr->second);

This approach is better, since it gets the speed from using a pointer, and the safety of not being able to get the pointer wrong since the function tells you where it is, not the other way around.

BUT … once you have the address of that static memory, you can modify it.

TimeStruct *timePtr;
timePtr = GetDateTime3 ();
timePtr->year = 1969;

In a real Date/Time function (like the one in the C library), those variables are populated with the system time when you call the function, so even if the user changed something like this, it would be set back to what it was the next time it was called. But, I can see where there could be issues with other types of functions that just hold on to memory like this.

Plus, it’s always holding on to that memory whether anyone is using it or not. That is a no-no when working on memory constrained systems like an Arduino with 4K of RAM.

Try #4: Returning a copy of a structure

And now the point of today’s ramblings… I rarely have used this, since it’s probably the slowest way to do things, but … you don’t just have to return a date type like and int or a bool or a pointer. You can return a structure, and C will give the caller a copy of the structure.

TimeStruct GetDateTime4 (void)
{
    TimeStruct time;
    
    time.year = 2023;
    time.month = 8;
    time.day = 19;
    time.hour = 4;
    time.minute = 20;
    time.second = 0;

    return time;
}

TimeStruct time;
time = GetDateTime4 ();    
printf ("GetDateTime4: %d/%d/%d %02d:%02d:%02d\n",
       time.year, time.month, time.day,
       time.hour, time.minute, time.second);

Above is possibly the safest way to return data, since no pointers are used. The called makes an new structure variable, and then the function creates a new structure variable and the return copies that structure in to the caller’s structure.

Try it: https://onlinegdb.com/F6rR1V-xb

This is slower, and consumes more memory during the process of making all these copies, BUT it’s far, far safer. Even ChatGPT agrees that, if going to “safe” code, this is the better approach.

And, at my day job, I experimented with this and it’s been working very well. It’s about the closest thing C has to “objects”. I even use it for a BufferStruct so I can pass a buffer around without using a pointer (though internally there is a pointer to the buffer memory). It looks something like this:

#include <stdio.h>
#include <string.h>

typedef struct
{
    char buffer[80];
    char bufferSize;
} BufferStruct;

BufferStruct GetBuffer ()
{
    BufferStruct buf;
    
    strncpy (buf.buffer, "Hello, world!", sizeof(buf.buffer));
    buf.bufferSize = strlen(buf.buffer);
    
    return buf;
}

void ShowBuffer (BufferStruct buf)
{
    printf ("Buffer: %s\n", buf.buffer);
    printf ("Size  : %d\n", buf.bufferSize);
}

int main()
{
    BufferStruct myBuffer;
    myBuffer = GetBuffer ();
    ShowBuffer (myBuffer);

    BufferStruct testBuffer;
    strncpy (testBuffer.buffer, "I put this in here",
             sizeof(testBuffer.buffer));
    testBuffer.bufferSize = strlen (testBuffer.buffer);
    ShowBuffer (testBuffer);
    
    return 0;
}   

The extra overhead may be a problem if you are coding for speed, but doing this trick (while trying not to think about all the extra work and copying the code is doing) gives you a simple way to pass things around without ever using a pointer. You could even do this:

typedef struct
{
    int year;
    int month;
    int day;
    int hour;
    int minute;
    int second;
} TimeStruct;

// Global time values.
int g_year, g_month, g_day, g_hour, g_minute, g_second;

void SetTime (TimeStruct time)
{
    // Pretend we are setting the clock.
    g_year = time.year;
    g_month = time.month;
    g_day = time.day;
    g_hour = time.hour;
    g_minute = time.minute;
    g_second = time.second;
}

TimeStruct GetTime ()
{
    TimeStruct time;

    // Pretend we are reading the clock.
    time.year = g_year;
    time.month = g_month;
    time.day = g_day;
    time.hour = g_hour;
    time.minute = g_minute;
    time.second = g_second;

    return time;
}

TimeStruct time;

time.year = 2023;
time.month = 8;
time.day = 19;
time.hour = 12;
time.minute = 4;
time.second = 20;
SetTime (time);

...

time = GetTime ();

And now a certain percentage of C programmers who stumble in to this article should be having night terrors at what is going on here.

Until next time…