path: root/posts
diff options
authorFranklin Wei <franklin@rockbox.org>2019-11-30 00:02:43 -0500
committerFranklin Wei <franklin@rockbox.org>2019-11-30 00:02:43 -0500
commit76e478608dac0149fae6283896083c86952a9984 (patch)
tree7d6d490988aa169ce2d929e8754e71e6cb94a3ba /posts
parent06fc8768876008f1529663567139d00544a653a6 (diff)
Make things look nice.
LaTeX math, footnotes, syntax highlighting!
Diffstat (limited to 'posts')
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
+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}
;; 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
+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}
*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
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
+[^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, engraved.](baby-yoda.png)
+![Baby Yoda, engraved. ([G-code](baby-yoda.nc))](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
The heart of my program is a function,
+[`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
+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.