diff options
Diffstat (limited to '')
| -rw-r--r-- | posts/adieu-quake.md | 81 | ||||
| -rw-r--r-- | posts/opening-black-boxes.md | 62 |
2 files changed, 106 insertions, 37 deletions
diff --git a/posts/adieu-quake.md b/posts/adieu-quake.md index 1d72f40..c167542 100644 --- a/posts/adieu-quake.md +++ b/posts/adieu-quake.md @@ -108,16 +108,18 @@ I traced the data flow back and found where it originated -- a call to `Q_atof()` -- the classic string to float converter. And then it dawned on me: I had provided a set of wrapper functions, which overrode Quake's `Q_atof()` -- and my `atof()` function must've been -broken. Fixing it was straightforward. I replaced the flawed `atof` -with a correct one. Et voila! The glorious three-passage introduction -level loaded flawlessly, and "E1M1: The Slipgate Complex" loaded fine -too. The sound output still sounded like a 2-cycle lawnmower, but hey --- I'd gotten Quake to boot on an MP3 player! +broken. Fixing it was straightforward. I +[replaced](https://git.rockbox.org/?p=rockbox.git;a=blobdiff;f=apps/plugins/sdl/wrappers.c;h=efa29ea7b852becf850e27f7e9d361e1862bf398;hp=ee512dd5737c0b0dfaac271396281d6ba2320dc5;hb=HEAD;hpb=3f59fc8b771625aca9c3aefe03cf1038d8461963) +my flawed `atof` with a correct one -- the one that shipped with +Quake. Et voilĂ ! The glorious three-passage introduction level loaded +flawlessly, and "E1M1: The Slipgate Complex" loaded fine too. The +sound output still sounded like a 2-cycle lawnmower, but hey -- I'd +gotten Quake to boot on an MP3 player! ## Down the Rabbit Hole This project finally gave me an excuse to do something I'd been -putting off for a while: learn ARM assembly language. +putting off for a while: learn ARM assembly language.[^1] The application was in a performance-sensitive sound mixing loop in `snd_mix.c` (remember the lawnmower-like sound?). @@ -131,7 +133,7 @@ out. Here's the assembly version I came up with (C version follows): -``` +~~~ {#asm-listing .gnuassembler .numberLines} SND_PaintChannelFrom8: ;; r0: int true_lvol ;; r1: int true_rvol @@ -173,11 +175,11 @@ SND_PaintChannelFrom8: ldmfd sp!, {r4, r5, r6, r7, r8, sl} bx lr -``` +~~~ There's some hackery going on here that could use some explaining. I'm using the ARM `qadd` DSP instruction to get saturation addition for -cheap, but `qadd` only works with 32-bit words, and the sound samples +cheap^[1](#asm-listing-25)^, but `qadd` only works with 32-bit words, and the sound samples are 16 bits. The hack, then, is to first shift the samples left by 16 bits; `qadd` the samples together; and then shift them back. This accomplishes in one instruction what GCC took seven to do. (Sure, I @@ -190,7 +192,7 @@ Notice also that I'm reading and writing two stereo samples at a time The C version is below for reference: -``` +~~~ {.c .numberLines} void SND_PaintChannelFrom8 (int true_lvol, int true_rvol, signed char *sfx, int count) { int data; @@ -207,7 +209,7 @@ void SND_PaintChannelFrom8 (int true_lvol, int true_rvol, signed char *sfx, int paintbuffer[2*i+1] = CLAMPADD(paintbuffer[2*i+1], data * true_rvol); } } -``` +~~~ I calculated about a 60% improvement in instructions/sample over the optimized C version. Most of the saved cycles come from using `qadd` @@ -223,24 +225,25 @@ will lead to an integer wraparound to `0xFFFFFFFF` and an extremely long delay (which will eventually resolve itself). This corner case was triggered by one sound in particular, of 7325 -samples in length (the sound triggered by a 100 health pickup, -incidentally). What's so special about 7325, you ask? Try taking it +samples in length.[^2] What's so special about 7325, you ask? Try taking it modulo any power of two: -``` -7325 % 2 = 1 -7325 % 4 = 1 -7325 % 8 = 5 -7325 % 16 = 13 -7325 % 32 = 29 -7325 % 64 = 29 -7325 % 128 = 29 -7325 % 256 = 157 -7325 % 512 = 157 -7325 % 1024 = 157 -7325 % 2048 = 1181 -7325 % 4096 = 3229 -``` +$$ +\begin{align*} +7325 &\equiv 1 &\pmod{2} \\ +7325 &\equiv 1 &\pmod{4} \\ +7325 &\equiv 5 &\pmod{8} \\ +7325 &\equiv 13 &\pmod{16} \\ +7325 &\equiv 29 &\pmod{32} \\ +7325 &\equiv 29 &\pmod{64} \\ +7325 &\equiv 29 &\pmod{128} \\ +7325 &\equiv 157 &\pmod{256} \\ +7325 &\equiv 157 &\pmod{512} \\ +7325 &\equiv 157 &\pmod{1024} \\ +7325 &\equiv 1181 &\pmod{2048} \\ +7325 &\equiv 3229 &\pmod{4096} +\end{align*} +$$ *5, 13, 29, 157*... @@ -255,6 +258,12 @@ isn't it? ## Adieu +In the end I ended up packaging this port up as a +[patch](http://gerrit.rockbox.org/r/#/c/1832/) and merging it into the +Rockbox mainline, where it resides today. It ships with builds for +most of the ARM targets with color displays in Rockbox 3.15 and +later.[^3] + I've omitted a couple interesting things here for the sake of space. There is, for example, the race condition that occured only when gibbing a zombie but only when the audio sample rate was 44.1 @@ -267,3 +276,21 @@ to squeeze out a few more frames. But those are for another time. For now, it is time to say goodbye to Quake -- it's been good to me. So long, and thanks for all the fish! + +[^1]: If you're interested in learning ARM assembly, Tonc's +[*Whirlwind Tour of ARM +Assembly*](http://www.coranac.com/tonc/text/asm.htm) is a good (albeit +slightly outdated and GBA-oriented) place to start. And while you're +at it, go ahead and get a printout of the [ARM Quick Reference +Card](http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001l/QRC0001_UAL.pdf). + +[^2]: It was the sound triggered by a [100 health +pickup](r_item2.wav), incidentally. + +[^3]: I honestly don't remember exactly which targets do and don't +support Quake. If you're curious, head over to the [Rockbox +site](http://rockbox.org) and try installing a build for whatever +target(s) you might have. And do [let me know](mailto:me@fwei.tk) how +it runs! New versions of [Rockbox +Utility](https://www.rockbox.org/wiki/RockboxUtility) (1.4.1 and +later) also support automatic installation of the Quake shareware. diff --git a/posts/opening-black-boxes.md b/posts/opening-black-boxes.md index 1857ed6..b944166 100644 --- a/posts/opening-black-boxes.md +++ b/posts/opening-black-boxes.md @@ -1,6 +1,6 @@ -# On Opening Black Boxes or: How I Learned to Stop Worrying and Love G-Code +# On Opening Black Boxes or: How I Learned to Stop Worrying and Love G-Code {#top} - +)](baby-yoda.png) **TL;DR** PhotoVCarve should not cost $149. I made [my own](https://github.com/built1n/rastercarve). @@ -28,14 +28,15 @@ that couldn't be done in a couple lines of Python,* I thought. The first step in the process was figuring out *how* to control a CNC machine. Some Googling told me that virtually all machines read -"G-code", a sequence of alphanumeric instructions that command the -movement of the tool in 3 dimensions. It looks something like this: +[G-code](https://en.wikipedia.org/wiki/G-code), a sequence of +alphanumeric instructions that command the movement of the tool in 3 +dimensions. It looks something like this: -``` +~~~ {.numberLines} G00 X0 Y0 Z0.2 G01 Z-0.2 F10 G01 X1.0 Y0 -``` +~~~ These three commands tell the machine to: @@ -57,25 +58,62 @@ perhaps a total of 4 hours from my initial proof-of-concept to the current viable prototype. There were no major hiccups this time around, and even though I'm still in the process of learning it, Python made things *so* much easier than C (or God forbid -- [ARM -assembly](adieu-quake.html)). +assembly](adieu-quake.html#asm-listing)). The heart of my program is a function, -[`engraveLine`](http://fwei.tk/git/rastercarve/tree/src/rastercarve.py?id=c2de4a3258c3e37d4b49a41d786eef936262f137#n118), +[`engraveLine`](http://fwei.tk/git/rastercarve/tree/src/rastercarve.py?id=c2de4a3258c3e37d4b49a41d786eef936262f137#n118) (below), which outputs the G-code to engrave one "groove" across the image. It takes in a initial position vector on the border of the image, and a direction vector telling it which way to cut. +~~~ {.python .numberLines} +# Engrave one line across the image. start and d are vectors in the +# output space representing the start point and direction of +# machining, respectively. start should be on the border of the image, +# and d should point INTO the image. +def engraveLine(img_interp, img_size, ppi, start, d, step = LINEAR_RESOLUTION): + v = start + d = d / np.linalg.norm(d) + + if not inBounds(img_size, v): + print("NOT IN BOUNDS (PROGRAMMING ERROR): ", img_size, v, file=sys.stderr) + + moveZ(SAFE_Z) + moveRapidXY(v[0], v[1]) + + first = True + + while inBounds(img_size, v): + img_x = int(round(v[0] * ppi)) + img_y = int(round(v[1] * ppi)) + x, y = v + depth = getDepth(getPix(img_interp, img_x, img_y)) + if not first: + move(x, y, depth) + else: + first = False + moveSlow(x, y, depth) + + v += step * d + # return last engraved point + return v - step * d +~~~ + After this was written, it was a simple exercise to write a driver function to call `engraveLine` with the right vectors in the right -sequence -- and that was all it took! (I really wonder how Vectric +sequence -- and that was all it took![^1] (I really wonder how Vectric manages to charge $149 for this...) I fired up the program on a test image and fed its output into -ShopBot's excellent G-code previewer. Success (see above)! I added a +ShopBot's excellent G-code previewer. [Success](#top)! I added a couple of tweaks (getting the lines to cut at an angle was fun) and I christened the program [*RasterCarve*](https://github.com/built1n/rastercarve). +The G-code that produced the image at the top of this post is +[here](baby-yoda.nc). Xander Luciano has an excellent online +[simulator](https://ncviewer.com) which can preview this toolpath. + ## Conclusion This was a fun little project that falls into the theme of "gradually @@ -83,3 +121,7 @@ opening up black boxes." G-code, I learned, isn't nearly as hard as it might seem. It's all too easy to abstract away the details of a technical process, but sometimes the best way to really understand something is by opening up the hood and tinkering with it. + +[^1]: I'm probably oversimplifying here. There was, in reality, some +neat vector math to figure out just *where* the "border" of the image +would be when the grooves were at an angle. |