X16 Basic: Output data values to file... Windows to view as "binary" (and not "text" file)... beginner's journey.

Chat about anything CX16 related that doesn't fit elsewhere
Post Reply
OldOzC64er
Posts: 8
Joined: Wed Jan 25, 2023 8:03 am

X16 Basic: Output data values to file... Windows to view as "binary" (and not "text" file)... beginner's journey.

Post by OldOzC64er »

As a "reliving-my-childhood" beginner programmer, I'm playing with X16 Basic to try out some ideas and general tinkering. I know many will know this next scenario - but I couldn't find a simple/sensible answer across all sorts of searching, so I thought it's worth showing my lack of knowledge for other's who are in the same situation.

I wanted to read in a (large) "text" file of 8-bit numbers (eg. 0-255), space delimited, and then process the data and output it as a binary file... yes, I know... they're all binary files. :roll: I mean, I wanted the values output to file to not be output as characters/text, but as actual single-byte values of what each value is for 0-255. For example, VS Code would see it as a "binary" file and load it into the Hex Editor*.

There are many internet references to "input" (loading) binary data into Basic, but not so much about the "output". ChatGPT4 suggests it's just not possible :P. Sure there's a brief overview of (confusing) codes in various website copies of the C64 "OPEN" command, and PRINT# command info - but not lots of details. To be fair, going back to the original manuals did have a bit more information on numbers being "spaced", but it focuses more on "string" examples and not "number values".

Anyway, I couldn't easily find any reference to the output "characters" and how it all works. I tried outputting the number values, hex values, strings, with mixed results - and then finally saw a reference to CHR$(value). The CHR$ output achieved what I needed, although it wasn't obvious to me Why at the time? Other than the ASCII code created will output a "value" outside the text range of characters (more easily). It seems more that the PRINT# command respects this converted value more than other interpretations of "values". As CHR$ output handles 0-255, this will trigger other applications to see the file as "binary" over an assumed "text" file (if I have numbers outside the text range).

For the beginner still reading... I found that the PRINT#, command was better at outputting from actual "string/character" values or variables, than number variables (although whilst writing this, I didn't try integer% variables?). The output of number data is converted to strings (just like the onscreen "PRINT" I assume), which is where it goes wrong. There is the classic "spaces" surrounding a number to screen. The "number/integer" is converted to a string as the actual representation of the value. So, instead of outputting the number variable value of 65 (as a single byte value), which might be interpreted by other applications as the letter A (in the file), it outputs : SPACE-SIX-FIVE-SPACE. These are stored as 4 characters of ASCII values 32-54-53-32 (or HEX 20-36-35-20)... which represent the "6" & "5" and not the required "A" or ASCII 65 (or HEX 41)... in a single byte. It appears there are different outputs depending on different methods of value storage and what Value you're working with. I don't know if there are other characters beyond "0" which are treated differently, I just wanted 1 method that worked for all numbers.

The following code might help in testing your own character output needs, or at least visually explains some variations I tested.
10 OPEN 1,8,2,"EXAMPLE,S,W"
15 A$="A":A=65
16 B$="0":B=0
17 C$="#":C=35
20 REM WRITES VARIATIONS OF VALUES/STRINGS/VARIABLES TO OUTPUT FILE
29 REM ----------------------------------- HEX codes
30 PRINT#1,A$ : REM                    Y = 41
31 PRINT#1,"A" : REM                   Y = 41
32 PRINT#1,A : REM                     N = 20 36 35 20
33 PRINT#1,65 : REM                    N = 20 36 35 20
34 PRINT#1,HEX$(ASC("A")) : REM        N = 34 31
35 PRINT#1,CHR$(ASC(A$)) : REM         Y = 41
36 PRINT#1,CHR$(ASC("A")) :REM         Y = 41
37 PRINT#1,CHR$(A) : REM               Y = 41
38 PRINT#1,CHR$(65) : REM              Y = 41
39 REM ----------------------------------- HEX codes
40 PRINT#1,B$ :REM                     N = 30
41 PRINT#1,"0" : REM                   N = 30
42 PRINT#1,B : REM                     N = 20 30 20
43 PRINT#1,0                           N = 20 30 20
44 PRINT#1,HEX$(ASC("0")) : REM        N = 33 30
45 PRINT#1,CHR$(ASC(B$)) : REM         N = 30
46 PRINT#1,CHR$(ASC("0")) :REM         N = 30
47 PRINT#1,CHR$(B) :REM                Y = 0
48 PRINT#1,CHR$(0) : REM               Y = 0
49 REM ----------------------------------- HEX codes
50 PRINT#1,C$ : REM                    Y = 23
51 PRINT#1,"#" : REM                   Y = 23
52 PRINT#1,C : REM                     N = 20 33 35 20
53 PRINT#1,35 : REM                    N = 20 33 35 20
54 PRINT#1,HEX$(ASC("#")) : REM        N = 32 33
55 PRINT#1,CHR$(ASC(C$)) : REM         Y = 23
56 PRINT#1,CHR$(ASC("#")) : REM        Y = 23
57 PRINT#1,CHR$(C) : REM               Y = 23
58 PRINT#1,CHR$(35) : REM              Y = 23
59 REM --------------------------------
60 CLOSE 1 
MarkTheStrange
Posts: 20
Joined: Sat Nov 26, 2022 6:24 pm

Re: X16 Basic: Output data values to file... Windows to view as "binary" (and not "text" file)... beginner's journey.

Post by MarkTheStrange »

PRINT# outputs the same thing as PRINT without #, just to the file instead of the screen. So if you have any questions about what you're going to get, just removing the "#fileno," is a great way to achieve clarity.

In general, both PRINT statements are primarily designed to create text for human consumption. So if you PRINT a number, you will get a decimal literal. This is no different than other programming languages; if you do print(65) in Python, you get the text "65" followed by a newline. CBM BASIC adds extra space around the number, though; the leading space is a placeholder for the minus sign, so positive and negative numbers with the same number of digits line up when printed in a column, while the trailing space is just there to separate numbers when a series is printed together. So PRINT#1,65 will output a total of five bytes to the file: space, '6', '5', space, carriage return.

If you want to write binary data, you have to go a little out of your way, which is where CHR$ comes in. That function takes a number in the range that fits in an unsigned byte (0-255 inclusive) and produces a one-character string consisting of the character with that ASCII/PETSCII code: in other words, that one-byte value. You also need to be sure to put a `;` at the end of the PRINT statement so that you don't get an extra byte with value 13 (the trailing carriage return):

Code: Select all

PRINT #1, CHR$(65);
OldOzC64er
Posts: 8
Joined: Wed Jan 25, 2023 8:03 am

Re: X16 Basic: Output data values to file... Windows to view as "binary" (and not "text" file)... beginner's journey.

Post by OldOzC64er »

Thanks for the added clarification... I realise I'm 40+ years late to the party, but I wanted to post my beginner's view (even discovering much of the fact that it's just "PRINT" with a purpose, during my post). It's hopefully for the future visitor trying to understand why there are certain outputs... so thanks for your added comment/s. In further searching for information, the original C64 User Guide and Reference manual have a bit more information, which seems to have been left out on many of the copied/duplicated reference websites of current. It mentions more about the "spaces" situation with numbers, but still doesn't quite highlight the CHR$ relationship with the single byte output. Anyway, I appreciate now that back in the day it was all about the "humans" and getting data/information out, in a readable format.

SIDE NOTE : My journey through the past, is somewhat like the X16 experience I have now, which is that much of the current information/explanation is "assumed". I understand it's based on a long history of knowledge (eg. C64), so the leading front (you keen enthusiasts :D ) have to assume a level of skill to get through a development process. I just find that many of the "Github" projects or references to how things should be, are often geared to people who are already "in the know". This makes sometimes the simplest task more obscure, as it's been passed into the "duh, everyone knows that, surely" level of awareness. All good. The X16 is a time-machine to my past. Thanks to all for promoting it.
MarkTheStrange
Posts: 20
Joined: Sat Nov 26, 2022 6:24 pm

Re: X16 Basic: Output data values to file... Windows to view as "binary" (and not "text" file)... beginner's journey.

Post by MarkTheStrange »

> doesn't quite highlight the CHR$ relationship with the single byte output.

Well, I don't want you to misconstrue anything here.

BASIC has no concept of "bytes". Bytes are a low-level feature; they live inside the workings of the computer and the BASIC interpreter. At the level of the BASIC language, there is nothing but numbers and strings. There are contexts where numeric values must be in a range that fits into an unsigned byte (string length, POKE values), and of course that's not a coincidence. But as an entity inside the language, bytes just don't exist.

Strings do exist, and strings are made up of characters. It's true that – again, much like Python, and unlike C – the individual characters that comprise a string are themselves strings of length one, rather than a separate "character" type. But they nonetheless map to individual bytes when doing I/O: if you read a byte, you get a 1-character string; if you print a 1-character string, you write a byte.

CHR$() is a very simple function that turns a number into the character at that code point. It's the same as chr() in Python, String.fromCharCode() in JavaScript, etc. But the X16 operates in a world where multibyte characters don't exist, so there's no encoding step, just a type transformation from number to string. Most importantly, if you print out the value of CHR$(N), what you get is the single byte whose numeric value is N. And if you read a string using GET or GET#, you will get a one-character string whose ASC() value is the numeric value of the byte read.

A final note: normally, if you're just copying bytes, you can ignore the numbers and just manipulate strings: GET#1, A$ followed by PRINT#2, A$; will copy byte-for-byte, without having to ever call ASC() or CHR$(). But that doesn't work for all byte values, unfortunately. BASIC I/O sometimes conflates a zero byte with the empty string; If the next byte to be read from the input channel has value 0, simply doing GET#1, A$ and then PRINT#2, A$; may print nothing at all to the output channel. So to be safe, you need to change the output statement to PRINT CHR$(ASC(A$)); instead.
mortarm
Posts: 299
Joined: Tue May 16, 2023 6:21 pm

Re: X16 Basic: Output data values to file... Windows to view as "binary" (and not "text" file)... beginner's journey.

Post by mortarm »

OldOzC64er wrote: Mon Sep 11, 2023 11:07 pm SIDE NOTE : My journey through the past, is somewhat like the X16 experience I have now, which is that much of the current information/explanation is "assumed". I understand it's based on a long history of knowledge (eg. C64), so the leading front (you keen enthusiasts :D ) have to assume a level of skill to get through a development process. I just find that many of the "Github" projects or references to how things should be, are often geared to people who are already "in the know". This makes sometimes the simplest task more obscure, as it's been passed into the "duh, everyone knows that, surely" level of awareness. All good. The X16 is a time-machine to my past. Thanks to all for promoting it.
I totally get this. However, at this stage of the game it's kind of a necessary evil. After all, it's still in the development stage, which means things are always changing. You don't want to present such a product (any product, for that matter) to "Joe Public" until it's in as close to a finished state as possible. To do otherwise would just cause frustration and confusion.
Post Reply