Apple II+ Hi-res
Nothing amazingly new here. Just having fun messing with old tech.
Hi-Res graphics
The old Apple II+ has low-resolution and high-resolution graphics. Eventually, the Apple //e would get "double hi-res".
High resolution for the Apple II+ is 240 pixels wide x 192 vertically. You enter into it with the HGR command, and then a special software switch (POKE -16302,0) tickles things so that the bottom lines are shown as graphics instead of text.
By default, the HGR command also clears the graphics RAM. There was a command called HCOLOR to set up which color would be assigned, and HPLOT x,y would set up a bit (or pair of bits?) in memory to turn a pixel on. The coordinate positioning was x=0 (left) to x=239 (right), y=0 (top) to y=191 (bottom).
There are two pages of RAM. The first stretches from $2000 to $3fff. The second page goes $4000 to $5fff.
The pixels on each line are not individually color-able, the way we see in recent tech. Instead, the Apple used a bitwise mechanism, whereby bit 7 (0x80 or 128 decimal) determined which color set would be rendered (through clever means affecting the video out wave), and then bits 6..0 (0x40 64, 0x20 32, 16, ..., 0x01 1) were pixel on/off settings. Combinations of those would yield different colors.
For my purposes, I just wanted to be able to take an image on a PC, and be able to send it to the Apple.
Cassette IN
I'd previously figured out how to use the Cassette interface of the Apple to LOAD a BASIC program, or BLOAD a binary set of data.
The code started with https://github.com/pyrex8/apple_ii_basic_2_wav. When it saves an example, tokenized BASIC program, there are two sections generated: program length and checksum in one, and then tokenized bytes and checksum in the other.
In each section there's a leading "tone", a 770Hz series of high and low values. That is followed by data sent at 20 kHz. It starts with a "short zero" sync bit (4 cycles low, 5 cycles high), and then each data byte's bits in most- to least-significant order. Data bytes are sent with a 0 bit, 5 cycles low and 5 cycles high, or a 1 bit, 10 cycles low and 10 cycles high.
Along the way, a checksum is computed starting with 0xff and then XOR'd with each emitted data byte. That checksum is sent at the end of the waveform.
To read the program, I connected headphone out on my Linux box to Cassette IN on the Apple II+. In Applesoft, I would just run the command LOAD, and then hit play in Audacity. It took some experimentation to get the volume right, or else the program wouldn't load.
I modified the code to read a binary file and generate a binary loadable waveform. Unlike the BASIC program, binary data is just one section, similar to the second section of a BASIC program. To read it on the Apple II, I would enter the monitor (CALL -151) and then at the "*" prompt, enter startAddr.endAddrR (e.g., 2000.3FFFR) before hitting Play in Audacity.
Data conversion
To generate the memory representation for Hi-Res graphics, I took data from several pages, eventually ending up with the code below.
The X computation is fairly easy. As an example, X values 0..6 are represented by bits 0..6 of a byte. Similarly, bits 7..13 are bits 0..6 of the next byte in the line, and so on. Bit 7, for my purposes, is always set to 0. In short, given a starting address for a line of bytes, use x/7 to get the byte offset, and then x mod 7 to get the bit.
The Y computation is where it's tricky. There are three "blocks" of memory for each of the 64 lines of graphics. The starting set (of page 1) starts at $2000. Rows 64..127 are at $2028. Rows 128..191 are at $2050.
Then, within each block, you have eight 8-line chunks. Each of those starts at an offset of 0x80 * multiplier.
So, line 0 is at $2000, line 8 starts at $2080, line 16 starts at $2100, and so on.
Similarly, line 64 is at $2028, line 72 starts at $20a8, line 80 starts at $2128, and so on.
Then, within each of those chunks, the y+1 offsets are a multiplier of 0x400.
Examples
0 $2000
1 $2400
8 $2080
9 $2480
64 $2028
65 $2428
etc.
In the end, it's a pretty simple computation with some divs, mods, bit shifting, etc. I'm guessing a tighter code representation can be found by inspecting the HPLOT code itself. Here's my python way of doing it. The "expensive" part is the div 7 and mod 7 that happens in the mapx() code. A pre-generated 240-wide lookup array could be used if I really cared, but today's machines are so fast that it doesn't really matter.
Given the mapxy function, it was pretty trivial to convert an image. I used Adobe Photoshop Elements to convert/downscale an image to 240x192 pixels, remove its color, and save.
Then, I used Python pillow (PIL) to load the image, iterate over all its pixels, and generated an in-memory representation as a bytearray. I just threw it together as a bytearray of $4000 size, not worrying about the fact that I wouldn't use the first half of the array. As I iterated over each x,y coordinate, I called mapxy to set up a byte address and bitmask. Then, I'd logically OR the bitmask over the existing bytearray byte.
When completed, I saved the bytearray as a .bin file.
The resulting file can be inspected, sort of, with the "od" command. The bitwise ordering isn't 100% representative of the visual representation, because of the 0x80 bit and LSB-to-MSB bit ordering, but at least the byte position can give a sense of where the black and white lines are.
From there, I could use the prior code to convert the .bin file to .wav form.
Since "od" only provides hexadecimal and octal output, I wrote a quick "readbits.py" to see the binary representation of the memory, and only printed non-zero values.
Then, a comparison with the waveform could be done. Here's an example. The lead-up 770Hz tone (6 seconds) and "short zero" bit and other leading bytes are not shown. In the lower window, you can see that at addresses $2039, $203a, ... we have bytearray values 0x38, 0x70, 0x7f, 0x7f, 0x0f. Those are seen here in 0 and 1 bit waveform.
Given those, I could run * 2000.3FFFR on the Apple monitor, and Play the .wav file from Audacity to get the image loaded.
In subsequent experiements, I started in DOS 3.3 and was able to BSAVE the binary value to a 5.25" floppy disk file. Then, I could write something like this:
10 PRINT CHR$(4), "BLOAD FILENAME"
to execute the BLOAD command from within a BASIC program.
No comments:
Post a Comment