Please sign in to access this page
A program that can render Bad Apple on a HP Prime G2 Graphing Calculator.
No followers yet
Once you ship this you can't edit the description of the project, but you'll be able to add more devlogs and re-ship it as you add new features!
Updated web port with YouTube demo videos and audio! The biggest challenge was probably to get the animation running at exactly 30fps and to have it sync with the audio. It turns out that requestAnimationFrame and setInterval aren't great for precise looping, so I had to do a little hack with a recursive setTimeout with a self-adjusting delay. But once that worked, getting the audio to sync was really easy. Still no real styling since I don't see a point in it. After all, the project is getting Bad Apple to render on the calculator, not the web.
Not a real devlog but a reminder that there are two demos! One is a video of the actual emulator (https://youtu.be/qDuJ1DxRWTY) and the other is the web port (https://draedon123.github.io/HP-Bad-Apple/), both found in the README of the GitHub repo.
Added a web port of the program! This was super simple to do since all the hard work has already been done. In fact, I wrote the port in one go and it worked first try, with no bugs, which is definitely a first. The port just uses the 2D Canvas API to render the frames so it should work on most, if not all devices. I didn't spend any time making it look pretty though, so it's just a canvas on a white screen.
After one (big) change, the file size of the compiled .hpprgm file has dropped by a whopping 62.7%! This brings it down from 98.8mb to 36.9mb. This was done by changing the internal representation of a frame from a list of numbers to a single binary string. So instead of having two numbers for one internal chunk of a frame being, say, 0,12743, which takes up 7 bytes, it now only requires two UTF-8 characters. The first one has a code point of 0+161=161 and the second 12743+161=12904. This takes up a total of 5 bytes. This by itself is only a 27% decrease, but I'm guessing the .hpprgm compiler can represent a binary string much more compactly than a list of numbers. Finally the program can run on my physical calculator... although it's only reaching 3-6fps depending on the frame, and has very visible screen tearing since the screen can't render the rows/columns fast enough.
Fixed the bugs. There were three main ones which all contributed to very slightly incorrect rendering. The first one was the easy one to track and fix. When my scripts read the individual frames from a folder, it didn't sort the files first. So everything would work well until you get to frame 99. Then, it renders frame 100, then 1000, and finally 101, because that's how the default alphabetical sorting goes. Next was I had the incorrect dimensions hardcoded. The screen is actually 320x240, but I misinterpreted a diagram and had 319x239. Finally, the last bug was an off-by-one error. Simply put, if I have to draw a horizontal line starting at (x, y) that's 5 units long, I need to draw it from (x, y) to (x + 4, y), not (x + 5, y). It renders smoothly on the emulator, but the built file is 98.8mb, which my physical calculator can't actually handle.
Optimisation is really hard to do. The first change was swapping LINEP to RECTP to draw lines. I'm guessing that RECT_P has less overhead and that's why it's faster. Then, to reduce the number of draw calls, I added a parameter (per frame) to determine which way to draw the rows. Some frames require much less draw calls if the direction is changed, and this reduced the build size by around 38%. It's still bugged though. Introducing this change made it so the bottom row of pixels doesn't work as expected, but I decided to just make a devlog before fixing it. All these optimisations brought up the fps on the physical calculator from 3fps to something I can't eyeball; it's almost smooth and just runs slower than it's supposed to.
Delta encoding was also very easy to get working. All I needed to do is store what pixels and render the changed pixels only, which massively improves performance at the cost of around a 2x increase in build size. I'm sure I can optimise the file size somehow though. I think delta encoding is a great optimisation for this project in particular because the Bad Apple video is just a simple animation, so not that many pixels change between frames as compared to other videos.
WARNING: VIDEO CONTAINS INTENSE FLICKERING. So video rendering does kind of work, because the functionality was actually implemented in the last devlog. But there are issues. Firstly, the flickering. The calculator just can't handle redrawing the entire screen every frame. I might have to fix this with a delta-based approach, where instead of storing the data for each individual frame, only the difference with the previous frame is stored and drawn. Next, the speed. It should be rendering at 30fps, but clearly that's not the case. And the last issue I found is that although it kind of runs smoothly on the emulator, it runs much slower on my actual calculator. Optimisations here I come.
Surprisingly wasn't hard to render a single frame! All it took was parsing the frames and drawing single-width lines across the screen, wrapping around to the next row when needed. Next step is rendering multiple frames and probably optimising
Wrote the algorithm to encode the video as an array usable by the HPPPL (HP Prime Programming Language, which the calculator uses). It first uses ffmpeg to get the individual frames of the video, and then uses JIMP (JavaScript port of GIMP) to resize the images. Then, all the pixels are converted to greyscale (a single number representing brightness). Pixels above a certain threshold (set to 200 right now) are assigned a value of 1. Otherwise, they get a 0. These values are then appended to a string in order, giving something like 0000000111000010. To compress this, Run Length Encoding is used, which basically writes out the character and how many times it's written in a row. In the above example, it would be 0,7,1,3,0,4,1,1,0,1. But since there's only two possible characters (0 and 1), I can omit the character and just write down how many times the character appears. Of course, the string has to be prepended by the starting character though, giving 0,7,3,4,1,1, which is much more compressed. This would be far better if I could write it out in raw bytes, but HPPPL can't parse binary.
Haha, I love the project!