Graphics by 4gentE
Music by Argon
Code by jmin
Pixeling is fun, but enhancing static pictures with animations and/or other tricks like raster bars has been on my todo list from day one. However, pixeling was so enjoyable that I kept postponing my dive into C64 ASM coding over and over again in the past months.
Similar to me pointing at Raistlin for become a scener, I am able to point to JackAsser for encouraging me to become a coder. His "Learn and do it. It would be AWESOME" reply to my Ceci n'est pas PETSCII pic never left my mind, so thank you both for being major signposts and the well-needed ass-kicks.
Before we jump into the story of my first released code, a short disclaimer:
While Smileymation is my first code released, it is not my first C64 ASM code per se. I started another project before this one, but it has become part of a larger F4CG group production, so it will see light of day sometime later. To document my journey in order of release, I have to acknowledge that parts of this release were not written from scratch; they either were lifted from the yet unreleased demo part or were advanced iteration of that code. Of course, there's new stuff too, so it's complicated. Waiting for the first code to be released would have been an option, but — as you surely have noticed — I'm somewhat of an impatient guy, so I just had to push it out there. Anyway, please read the following with that in mind.
When 4gentE's Among the Living pic popped up on CSDb I knew I needed to sit down and create a short color cycle animation for it. I mean, the pic is perfect, right? Similar to my Color GP PETSCII, the parts to splash some colors onto are all there already; I just need to update them. Boldly, I went ahead and asked via comment for an OK, which I received, so my second coding project was officially green-lit. Hooray!
My first goal as a coder was to write a simple PETSCII viewer. The main reason is that PETSCII Editor's export isn't really the best, as it includes all the charset data even if the default charset is used, which leads to unnecessarily big .prg files.
Sure, there are tools out there that can convert .pngs into .prgs (complete with all the bells and whistles like crunching), but I set out to learn C64 ASM, so I needed something easy to begin with anyways. Thus, turning the raw bytes of chars and colors into a pic it is!
With zero ASM knowledge, I started by exploring the tools' .asm exports. BASIC starter? Check. A loop for drawing the chars and colors? Check. Let's see if I can write my own loop for reading 40x25 bytes of char data and 40x25 bytes of color data, and then shoving them into the correct screen memory.
What I finally ended up with are the following two functions doing exactly that: The first function reads char bytes stored in petscii_char and pushes them onto the screen:
drawPETSCII: lda petscii_char,x sta $0400,x lda petscii_char+$0fa,x sta $04fa,x lda petscii_char+$1f4,x sta $05f4,x lda petscii_char+$2ee,x sta $06ee,x inx cpx #$fa bne drawPETSCII rts
The second function is quite similar; it reads the color bytes from petscii_color and pushes them into memory to transform the already rendered PETSCII on screen from monochrome to rainbow.
drawColor: lda petscii_color,x sta $d800,x lda petscii_color+$0fa,x sta $d8fa,x lda petscii_color+$1f4,x sta $d9f4,x lda petscii_color+$2ee,x sta $daee,x inx cpx #$fa bne drawColor rts
"But jmin, why two separate functions when it could be one single loop" I hear you ask? Well, drawing a PETSCII is where I started, but my real goal is to bring it to life by either updating the chars or the colors.
At first, I reopened PETSCII Editor and started redoing the colors for each frame needed. It's of course stupid, but then again, I had to start somewhere and by using the editor, I could experiment with color combinations and see how the colors would flow through the pic.
With the frame data in hand, I build a big fat loop that would update the entire screen's color data, thus creating my first animation. It worked, of course, but at the same time, it consumed a lot of memory, causing the created .prg to multiply in size.
So, what if I defined a sequence of colors to cycle through and used this array when updating the PETSCII? Which part of the pic to updated can be extracted from the single color frames (actually, singular, because the frames I made already followed a sliced-up image). Jep, that's more like it.
colorGradient: !byte 00, 06, 11, 04, 12, 05, 15, 13, 15, 05, 12, 04, 11, 06, 00
Next up, slicing the PETSCII and writing down the addresses of each slice.
I'm sure there's a tool out there for generating lists of STA commands based on sections of a screen, but I'm also sure that's a job that can be done in Excel within minutes. So, let's fire it up!
All I have to do now is slice up the pic by filling in the slice numbers. The rest is done automatically and just needs to be copied over into my .asm file.
ccycle: ldx currentFrame ; draw slice 01 lda colorGradient,x sta $d803 sta $d804 sta $d822 sta $d823 : ; draw slice 02 inx lda colorGradient,x sta $d805 sta $d806 sta $d807 sta $d81f : ; draw slice XX :
There's a bit more going on here, like looping through all of the colors of the defined gradient, and so on, but that's more or less how it works.
Overall, Smileymation is composed of five color bits: two static (the initial one following 4gentE's pic and a grey variant), and the following three color cycle loops:
Wait, you're seeing four different loops? Nah, one of the four shown isn't really there; it's just combining the first two.
Color cycling is neat, but I also wanted to animate the pic itself. I kept it simple, with only two chars being updated throughout the sequence: the center smiley's eyes!
For this, no elaborate code or Excel is needed. It all comes down to a couple of small functions that update the two chars in question with specific PETSCII codes.
eyesOpen: lda #$d1 sta $05CA sta $05CC rts eyesClosed: lda #$ad sta $05CA sta $05CC rts eyesInsane: lda #$d7 sta $05CA sta $05CC rts eyesLU: lda #$7a sta $05CA sta $05CC rts eyesRU: lda #$4c sta $05CA sta $05CC rts eyesLD: lda #$50 sta $05CA sta $05CC rts eyesRD: lda #$4f sta $05CA sta $05CC rts
With these in place, all I need to do is calling the variant that's needed, and we're good.
Next up: getting the timing right to create all the animations and keep it all running smoothly.
To more experienced coders: please note that I (still) don't really know what I'm doing here, and clean IRQs are still way over my head. However, the following is the first (and easiest) method I found for adding a timing function: waiting for a specific raster being drawn and thus calling my code at the same time each frame (or actually returning to my code at the same time each frame):
wait: lda timingFlag cmp $d012 bne wait lda $d012 wait_nextLine: cmp $d012 beq wait_nextLine rts
Basically, my idea was to trigger the wait whenever I'm done with an action for a frame. Calling it again would add a delay of exactly one frame, which works perfectly for syncing everything up and adding a pause when needed.
With that in place, I could write a short intro sequence as well as the main loop.
intro: ldx #$00 jsr drawPETSCII ldx #$00 jsr drawColor jsr wait jsr eyesClosed jsr wait jsr eyesOpen jsr wait jsr eyesClosed jsr wait jsr eyesOpen jmp mainLoop
To keep the example code above simple, I reduced the number of waits to a single one. However, waiting just a single frame would, of course, be way too fast. Initially, I added as many jsr wait as needed, but later, I extended my wait function to accept a parameter that defines how many frames should be waited for in a single call.
In the mailLoop, I'm calling the color cycle functions in sequence, with the various eye movements in-between. Nothing magical happening here.
So, animating the pic is great and all, but it feels incomplete without some .sid music playing, right? I'm not a metal-head, but the mates at F4CG pointed out that the PETSCII is a reference to Anthrax's album Among the Living, so adding an Anthrax tune seemed fitting.
Goerp dug up Got the time (which isn't on that album, and, fun fact, it's not even an original Anthrax tune, but let's not get be too nerdy here, OK?), and that's the track I downloaded for my mission to get some music playing.
AFAIK playing a .sid isn't too difficult — all it takes is calling its play function every frame. Ha! With my wait function already in place, was just a matter of adding a single line of code and voilĂ , that was enough to get this banger of a song working. Well, it does need to be initialized first, and I had to shift things around in memory to fit the .sid data, but let's just say it was as simple as adding a jsr $1003, shall we?
With everything working, my final task was to clean up all the messy bits that I've added along the way. As mentioned before, the wait function needed to support a duration parameter, so instead of having dozens of jsr wait calls, I simply set the duration and call the wait once.
Additionally, I tweaked the gradients for a smoother flow, especially the last, larger one. While cleaning it up, I got the idea to speed up the last gradient, and again, it was just a matter of reducing the number of wait calls going from 3 to 2 to 1, with the last one becoming lightning fast. Love it!
Up until now, I hadn't paid any attention to PAL/NTSC differences, so I had all the emulators I used over the years set to PAL. Being around CSDb for some time now, I did of course see all the "NTSC fixed" releases, I therefore decided to take a peek what's needed here exactly. As we all konw, there's an extra 10hz which of course does speed up the animation and the music because they're both tied to frames. Now, one could argue that watching Smileymation on a NTSC machine is just booting up a "speed metal" version, but I'm not sure that joke would land. So, let's figure out how to fix the music.
On Codebase64, I found the following small function that checks whether a PAL or NTSC system is used:
l1: lda $d012 l2: cmp $d012 beq l2 bmi l1 cmp #$20 bcc ntsc
OK, that looks simple! A loop that branches to ntsc if it's a NTSC machine. I can work with that!
To slow down the music, the easiest way is to skip a beat every 6th frame and that's exactly what I implemented. I'm using the PAL/NTSC function above for setting a variable palNtsc: if it's 0 (PAL), the music is called every frame; if it's not 0 (NTSC), I decrement it, and when it hits 0, it's time to skip the music for that frame and reset the variable for another countdown. There's probably a more elegant solution out there, but for my first dabbling into coding, I'm just happy it works.
music: ldx palNtsc cpx #$00 beq playmusic dex stx palNtsc cpx #$00 bne playmusic ldx #$07 stx palNtsc rts playmusic: jsr $1003 ; play the music rts
Skipping a frame might be noticeable to a trained ear, but for my first implementation, I'd say it's good enough.
For the animation, I don't change a thing. Having it running at 60hz is as beautiful as running it at 50hz I'd say.
What a fun project! While my code isn't necessarily how things are usually done, it does work, and I did learn a lot while writing it. There's plenty of room for improvement, and I can't wait to dive into handling timing (and IRQs) differently, but for a first release, I'm really happy with it.
fun stuff! I'd call it a one-filed demo but who cares
please update your function in F4cg: graphician... and coder! :)
cool one!!
Thx, updated both