Over on Hacker News there was an entry about the "awful" Amiga Kickstart 1.x icon and why it looked the way it did. This led to a link over on Stack Overflow where it was revealed that this was graphic actually drawn in a vector style, as opposed to raster pixels.
They also provided the actual bytes used to render the graphic. I thought it'd be fun to write a little parser to render these bytes.
And so did several other people, apparently, as I'm discovering now. :|
Anyway, here's my humble Codepen. And if that goes down, the original code is below the embed. 😎
(I skipped flood fill because I used two.js for this, and didn't realize until it was too late that I'd chosen poorly. Easy enough to swap out the graphics library, but it's time to move on.)
See the Pen Amiga Kickstart vector parser by Toby D (@Fortyseven) on CodePen.
/*
Inspired by:
https://retrocomputing.stackexchange.com/questions/13897/why-was-the-kickstart-1-x-insert-floppy-graphic-so-bad/13901
2021-03-06
*/
const floppy = [
0xFF, 0x01, 0x23, 0x0B, 0x3A, 0x0B, 0x3A, 0x21, 0x71, 0x21, 0x71, 0x0B, 0x7D, 0x0B, 0x88,
0x16, 0x88, 0x5E, 0x7F, 0x5E, 0x7F, 0x38, 0x40, 0x38, 0x3E, 0x36, 0x35, 0x36, 0x34, 0x38,
0x2D, 0x38, 0x2D, 0x41, 0x23, 0x48, 0x23, 0x0B, 0xFE, 0x02, 0x25, 0x45, 0xFF, 0x01, 0x21,
0x48, 0x21, 0x0A, 0x7E, 0x0A, 0x8A, 0x16, 0x8A, 0x5F, 0x56, 0x5F, 0x56, 0x64, 0x52, 0x6C,
0x4E, 0x71, 0x4A, 0x74, 0x44, 0x7D, 0x3C, 0x81, 0x3C, 0x8C, 0x0A, 0x8C, 0x0A, 0x6D, 0x09,
0x6D, 0x09, 0x51, 0x0D, 0x4B, 0x14, 0x45, 0x15, 0x41, 0x19, 0x3A, 0x1E, 0x37, 0x21, 0x36,
0x21, 0x36, 0x1E, 0x38, 0x1A, 0x3A, 0x16, 0x41, 0x15, 0x45, 0x0E, 0x4B, 0x0A, 0x51, 0x0A,
0x6C, 0x0B, 0x6D, 0x0B, 0x8B, 0x28, 0x8B, 0x28, 0x76, 0x30, 0x76, 0x34, 0x72, 0x34, 0x5F,
0x32, 0x5C, 0x32, 0x52, 0x41, 0x45, 0x41, 0x39, 0x3E, 0x37, 0x3B, 0x37, 0x3E, 0x3A, 0x3E,
0x41, 0x3D, 0x42, 0x36, 0x42, 0x33, 0x3F, 0x2A, 0x46, 0x1E, 0x4C, 0x12, 0x55, 0x12, 0x54,
0x1E, 0x4B, 0x1A, 0x4A, 0x17, 0x47, 0x1A, 0x49, 0x1E, 0x4A, 0x21, 0x48, 0xFF, 0x01, 0x32,
0x3D, 0x34, 0x36, 0x3C, 0x37, 0x3D, 0x3A, 0x3D, 0x41, 0x36, 0x41, 0x32, 0x3D, 0xFF, 0x01,
0x33, 0x5C, 0x33, 0x52, 0x42, 0x45, 0x42, 0x39, 0x7D, 0x39, 0x7D, 0x5E, 0x34, 0x5E, 0x33,
0x5A, 0xFF, 0x01, 0x3C, 0x0B, 0x6F, 0x0B, 0x6F, 0x20, 0x3C, 0x20, 0x3C, 0x0B, 0xFF, 0x01,
0x60, 0x0E, 0x6B, 0x0E, 0x6B, 0x1C, 0x60, 0x1C, 0x60, 0x0E, 0xFE, 0x03, 0x3E, 0x1F, 0xFF,
0x01, 0x62, 0x0F, 0x69, 0x0F, 0x69, 0x1B, 0x62, 0x1B, 0x62, 0x0F, 0xFE, 0x02, 0x63, 0x1A,
0xFF, 0x01, 0x2F, 0x39, 0x32, 0x39, 0x32, 0x3B, 0x2F, 0x3F, 0x2F, 0x39, 0xFF, 0x01, 0x29,
0x8B, 0x29, 0x77, 0x30, 0x77, 0x35, 0x72, 0x35, 0x69, 0x39, 0x6B, 0x41, 0x6B, 0x41, 0x6D,
0x45, 0x72, 0x49, 0x72, 0x49, 0x74, 0x43, 0x7D, 0x3B, 0x80, 0x3B, 0x8B, 0x29, 0x8B, 0xFF,
0x01, 0x35, 0x5F, 0x35, 0x64, 0x3A, 0x61, 0x35, 0x5F, 0xFF, 0x01, 0x39, 0x62, 0x35, 0x64,
0x35, 0x5F, 0x4A, 0x5F, 0x40, 0x69, 0x3F, 0x69, 0x41, 0x67, 0x3C, 0x62, 0x39, 0x62, 0xFF,
0x01, 0x4E, 0x5F, 0x55, 0x5F, 0x55, 0x64, 0x51, 0x6C, 0x4E, 0x70, 0x49, 0x71, 0x46, 0x71,
0x43, 0x6D, 0x43, 0x6A, 0x4E, 0x5F, 0xFF, 0x01, 0x44, 0x6A, 0x44, 0x6D, 0x46, 0x70, 0x48,
0x70, 0x4C, 0x6F, 0x4D, 0x6C, 0x49, 0x69, 0x44, 0x6A, 0xFF, 0x01, 0x36, 0x68, 0x3E, 0x6A,
0x40, 0x67, 0x3C, 0x63, 0x39, 0x63, 0x36, 0x65, 0x36, 0x68, 0xFF, 0x01, 0x7E, 0x0B, 0x89,
0x16, 0x89, 0x5E, 0xFE, 0x01, 0x22, 0x0B, 0xFE, 0x01, 0x3B, 0x0B, 0xFE, 0x01, 0x61, 0x0F,
0xFE, 0x01, 0x6A, 0x1B, 0xFE, 0x01, 0x70, 0x0F, 0xFE, 0x01, 0x7E, 0x5E, 0xFE, 0x01, 0x4B,
0x60, 0xFE, 0x01, 0x2E, 0x39, 0xFF, 0xFF
];
class AmigaVectParser {
constructor(bytes, elem) {
this.palette = ["#FFFFFF", "#000000", "#7777CC", "#BBBBBB"];
this.offset = [0,0];
this.prevOffset = [0,0];
this.curColor = 0;
this.isDrawing = false;
this.buffer = bytes || [0xff, 0xff];
this.done = false;
this.two = new Two({ width: 640, height: 400 }).appendTo(elem);
}
doCmd(cmd_pair) {
if (cmd_pair[0] === 0xff) {
this.isDrawing = false;
if (cmd_pair[1] === 0xff) {
// cmd_done
this.done = true;
return;
} else {
// cmd_colorSet
this.curColor = cmd_pair[1];
return;
}
} else if (cmd_pair[0] === 0xfe) {
// cmd_floodFill
this.isDrawing = false;
this.pointer += 2; //TODO FLOOD FILL
return;
}
if (!this.isDrawing) {
// first coordinate in a poly-line
this.prevOffset[0] = cmd_pair[0];
this.prevOffset[1] = cmd_pair[1];
this.isDrawing = true;
return;
} else {
// continuing the poly-line
this.offset[0] = cmd_pair[0];
this.offset[1] = cmd_pair[1];
let line = this.two.makeLine(
this.prevOffset[0] * 2,
this.prevOffset[1] * 2, // doubling up X/Y to make it easier to see at 640x400
this.offset[0] * 2,
this.offset[1] * 2
);
line.stroke = this.palette[this.curColor];
line.linewidth = 1;
this.prevOffset[0] = this.offset[0];
this.prevOffset[1] = this.offset[1];
}
}
draw() {
let cmd = [0, 0];
let pointer = 0;
this.done = false;
while (!this.done) {
cmd[0] = this.buffer[pointer++];
cmd[1] = this.buffer[pointer++];
this.doCmd(cmd);
}
this.two.update();
}
}
renderer = new AmigaVectParser(floppy, document.getElementById("draw-shapes"));
renderer.draw();
(This is a mirror from swtpc.org [archive.org], which itself is a mirror from BYTE Magazine. Minor formatting changes have been introduced.)
BYTE's Audio Cassette Standards Symposium
Written by Manfred and Virgina Peschke
BYTE, Feb 1976, Pages 72 and 73
BYTE Magazine sponsored a symposium on November 7 and 8, 1975 in Kansas City MO regarding the interchange of data on inexpensive consumer quality audio cassette drives.
These drives may be used as one of the mass storage devices in the first generation of personal computers, and will retain importance for some time to come as a means of interchange of software between computer enthusiasts who purchase products of the small systems industry.
In order to promote the growth of the industry, BYTE sought to achieve an industry standard on audio cassette data interchange through a working conference.
We extend our greatest appreciation to the 18 people who worked very hard until late Friday night and Saturday morning to discuss the multitude of problems and solutions associated with digital recording on auto cassettes. The names of the participants are listed in Table 1.
In spite of the short time available, the participants were able to draft a set of provisional standards which seems to promise great reliability and is rather inexpensive to implement; implementations may be entirely in hardware, or may require a mix of software and some minimal hardware.
Considerations were given to the problems of speed variation among recorders and playback equipment, start and stop delays, recording density (or speed) versus reliability, and recording frequencies to avoid interference with the telephone network in case some users plan to transmit the tones of the cassette over the phone lines.
On Saturday afternoon, Mr. Felsenstein and Mr. Mauch volunteered to write up the consensus among the participants as to a provisional standard which has been reproduced below.
Provisional Audio Cassette Data Interchange Standard
The consensus among the participants of the audio cassette standards symposium at Kansas City MO sponsored by BYTE Magazine is as follows:
Table 1: Participants at Audio Cassette Symposium.
Ray Borrill | 1218 Prairie Dr, Bloomington IN |
Hal Chamberlin | The Computer Hobbyist, P 0 Box 5985, Raleigh NC 27607 |
Tom Durston | MITS, 6328 Linn NE, Albuquerque NM |
Lee Felsenstein | LGC Engineering, 1807 Delaware St, Berkeley CA 94703 |
Joe Frappier | Mikra-D, 32 Maple St, Bellingham MA |
Bill Gates | MITS |
Gary Kay | Southwest Technical Products Corp, 219 W Rhapsody, San Antonio TX 78216 |
Bob Marsh | Processor Technology, 2465 Fourth St, Berkeley CA 94710 |
Harold A Mauch | Pronetics, 4021 Windsor, Garland TX 75042 |
Bob Nelson | PCM, San Ramon CA |
George Perrine | HAL Communications Corp, Box 365, Urbana IL 61801 |
Ed Roberts | MITS |
Richard Smith | The Computer Hobbyist, P 0 Box 5882, Raleigh NC 27607 |
Les Solomon | Popular Electronics, 1 Park Av, New York NY 10016 |
Michael Stolowitz | Godbout Electronics, Box 2355, Oakland Airport CA 94614 |
Paul Tucker | HAL Communications Corp |
Mike Wise | Sphere, 791 S 500 W, Bountiful UT 84010 |
Bob Zaller | MITS |
So, a couple weeks ago I was watching this video tribute to Super Mario World's 30th anniversary.
At around the 17:20 mark, in the middle of talking about various tie-in products to promote the game, it brings up Mario & Yoshi's Adventure Land. A one-episode animated movie that follows Mario and Luigi through, essentially, the events of Super Mario World.
What makes it unique is that it this is a "VCR game" of sorts that uses the Terebikko: an interactive 'quiz' device that mimics a telephone. Mario calls you. The phone rings. You pick it up. He asks you a question that needs a 1, 2, 3, or 4 response. (Or red, green, yellow, blue.)
You press the answer within the allotted time, and you get a response. (Near I can tell, it mutes the phone for the inappropriate response, but that's something we're going to find out definitively.)
And it's more than just Mario. There's a whole catalog of videos made for it in Japan, including Dragon Ball Z and Sailor Moon.
I found it all oddly fascinating. And my curiosity started to kick in. It seemed so simple, but it was a clever idea. I loaded the audio into Audacity and realized I could make out binary... uh oh.
See, one of the things I've always had an interest in, but never got a chance to try was demodulation of a digital signal from an audio file. Like the screeching of a modem, or a game loaded off an audio tape into a ZX Spectrum. That kind of thing. This seemed like the perfect on-ramp for it.
With very little actual information online, this also seemed like a perfect reverse engineering project in general.
I found out they released a version of this in the United States in 1989 under the Mattel label, a year after it's debut in Japan from Bandai, and... I found one on eBay for under $20 shipped. 😎
So now I'm, seemingly, irrevocably committed to this project, now that money is involved. 😏
Here's what I'm planning. I've already spent a couple days dicking around and have a stack of notes. I'm hoping to get at least several decent blog posts out of this adventure:
That last one is unlikely, but hey, if I haven't burned myself out on the entire thing by that point, who knows?
In my day job, I do a great deal of development on WordPress sites. I've come a long way in my understanding of the popular CMS software in the last couple years, but I'm still learning something new all the time.
In that spirit, here's a collection of WordPress plugins that I've found to be life changers. I consider them must-installs when working on new sites:
I remember when I started creating custom post types and using ACF. It expanded my perception of what a WordPress site could be. Before discovering ACF, I had to resort to ugly hacks and just kind of lumping posts together with categories and duct tape.
The base ACF is free, but the Pro version is not only an extremely fair price (A$25 at the time of writing), but includes invaluable field types, like the Repeater, Options pages, and the ability to integrate with the new Blocks feature in WP5.
This one may have been abandoned, but it still works as of this writing. It checks for plugin, theme, and core updates regularly, and emails me about it. WordPress being kept updated is crazy important, so it's great to be able to update as soon as possible. Might need to find a replacement for it, though, if it really has been left for rats.
If you're doing any work with user roles, you need this. View Admin As allows you to switch between various roles and capabilities without having to log out, or keep a private browsing window open with another account loaded up.
Which template wound up being loaded? Why is this taking so long to load? WHERE ARE MY PANTS?? Query Monitor adds a wonderful dropdown on the admin bar that helps you find out the answer to all of these, and more.
Dumps all of a post's meta values at the bottom of it's edit screen. See every little bit of information being stashed along side your posts. Why something like this isn't included as a built-in debug tool is beyond me.
Adds the wonderful vd()
, vdd()
debug methods (substitutes for print_r
, et al), and a gorgeous, very helpful error message via the Whoops error handler.
Just a word of warning though: this seems to crank the error reporting level, so even basic issues stop everything in their tracks. Normally, this is good -- fix it! But I had at least one time where it caused a silent crash, and I couldn't do ANYTHING in WordPress. Not even disable Debug Toolkit. I had to go in and manually remove the plugin from the command line. MOST of the time, it's perfectly great though. Just stay aware.
An interactive PHP console via the admin bar! Instead of hacking in a test or two and dying somewhere, just pop down the console and test out your PHP/WordPress code assumptions in a safe space.
Just wanted to document an interesting issue I had with Advanced Custom Fields recently...
Don't get me wrong, I swear by ACF on all of my WordPress projects. I consider it an absolute necessity. It's powerful, easy to use, and is priced reasonably.
It's when you go outside the standard usage of ACF that things get a bit... thorny...
Normally, you'd create a field group. And you'd assign that field group based on a set of criteria (a particular page template, or a custom post type, etc.).
Then you create a new post, and you're presented with your custom fields. You enter your data, and it's attached to the post when you save. And accessing that data is trivial from templates.
But what happens if you create a post with custom fields, but you're doing it from inside a PHP function, using wp_insert_post
?
What if you want to add content to a field in that post immediately afterward? Surprise! Your standard update_field
or add_row
code will silently fail. According to the documentation, you need to reference the field name using the field key
in that situation.
The field’s key should be used when saving a new value to a post (when no value exists). This helps ACF create the correct ‘reference’ between the value and the field’s settings.
ACF stores all of it's custom field values inside standard WordPress meta fields inside the post. In fact, you could just as easily use get_post_meta
to retrieve ACF field values under many circumstances. Or even write it back.
But ACF is much more than just a key/value pair, of course. Each field has a whole host of information associated with it. Label name, conditional display logic, field type, etc.
In the post's meta data, ACF creates two different values: the field name, and a field key reference.
Let's say I have a custom, basic Text-type field called "Title".
Inside the post, there will be a title
meta data field; this holds the actual value of the field. And then there's a _title
field. The underscore means it's a field key reference. The value of that looks something like field_123456
. Each field group entry gets it's own unique field key name that looks like that.
Internally, when you call get_field('title')
ACF looks up the post meta with an underscore -- _title
-- and uses that to pull up the details in the custom field group entry.
If you call get_field('field_123456')
, in fact, it will work as well. ACF will reference the field group info, and return the appropriate post meta that way.
Both are valid ways to work with ACF field content.
A brand new post, inserted with wp_insert_post
is completely blank. It has no post meta data, outside of the usual timestamp and creation info.
So if you try to run update_field('title', 'My Title', 9999)
, it does nothing. As if it doesn't exist. Because as far as ACF is concerned, it doesn't.
Not yet.
There's no _title
for it to reference for guidance.
But if you update_field('field_123456', 'My Title', 9999)
, it WILL work. ACF knows right where to go to get it's field details, and it works as normal.
That's all well and good for a simple Text field type.
But what if I have something more complicated? What if I have a Group type, with a Repeater inside that?
Let's say I have a Group called "Vehicles" and a Repeater inside that called "Trucks". (And presumably a "Cars", "Motorcycles", etc, but let's keep it simple!)
And each row inside "Trucks" has a "Model" Text field, and a "Mileage" Number field.
Under normal circumstances I could do:
add_row('vehicles_trucks', ['model'=>'Bigfoot', 'mileage' => '50000'], 9999)
. _(Note the special, barely documented vehicles_trucks
underscore selector notation for these nested fields. )_
But if I've just inserted the post, none of those key field references exist!
The vehicles_trucks
selector doesn't work. But the previous fix, using the raw key field reference... say, `add_row('field_902100'...``, well, that doesn't work either! Because which field key reference makes sense in this situation? The one for Vehicles? The one for Trucks?
If you use the key_
field key for Vehicle, it fails. Vehicle is a Group type. Nothing happens.
If you use the key_
field key for Trucks, however, something weird happens. Instead of creating a _vehicles_trucks
key reference, it creates a _trucks
reference...
At this point it's important to note that ACF is smart and slick... right up until the point it is not.
From what I've discovered, there is no shortcut to adding a new row to a Repeater field nested inside a Group if you've created the post inside PHP, before someone had a chance to hit 'Save' on it from the admin.
If you try to get clever, you might fairly think that underscore notation might apply here. Maybe stick the two together, like field_111111_ field_22222
.
No, we have to manually create all of ACF's key references ourselves before we can do anything:
update_post_meta(9999, '_vehicle', 'field_111111');
update_post_meta(9999, '_vehicle_truck', 'field_222222');
THEN we can add_row('vehicles_trucks', ...
and insert our data programmatically as expected.
This holds true for even deeper nested content, but at that point maybe you want to rethink what you're doing. 😉
I was surprised by just how little information about this specific use case exists. Hopefully this helps somebody out there!
Hit me up on Twitter if I've made an error anywhere in here.