See Procreate thumbnails on Linux

This is old thing I made that I never wrote about here: a thumbnailer for Procreate files. Procreate is a popular iPad drawing and painting app that has its own native *.procreate file format. Most people probably never have to think about this since you can’t access files directly on an iPad and PSD is probably more commonly used for interchange, but it’s pretty straightforward to create thumbnails for Procreate files.

The Procreate files themselves are zip archives and contain a preview image. The thumbnailer just needs to extract the image. Instructions for installing it are in the README file in the linked repo.

MacSD SCSI adapter

My Macintosh SE had the original 20MB hard drive still full of software, and although it was painfully slow (too much bloat, maybe?) I didn’t want to lose anything on it. That meant I needed to get a new hard drive that I could play around with.

Since the Macintosh SE uses SCSI, I wanted to get a SCSI to flash memory card adapter, of which several exist. There is the SCSI2SD, the open-source BlueSCSI, and a newer one called MacSD.

As far as I can tell, MacSD only popped online late 2020. Maybe it was a pandemic project? So the number of people using it is relatively small, but there were a couple key things that made me buy it over the others.

It features a lot of flexibility in how you manage your images. You can edit the macsd.ini file to define a “CD changer” with multiple CD images — just eject to go to the next CD. And you can load a bunch of assorted floppy images in various formats into a “composite” device.

The other nifty feature is MacSD Commander, which lets you copy files from the SD Card’s FAT32 partition directly into System 6/7. You just copy files straight onto the SD card from your modern Windows/Linux/Mac computer and then transfer them on your classic Mac.

I did run into some bugs but the creator was very responsive, worked with me to fix them, and even gave me some credit online 🙂

Screenshot from

MacSD is not an open-source product, but it might be the right one for your classic SCSI-equipped computer!

Should I use swap on Linux?

TL;DR: I turned off swap on my machine and it’s been fine.

I haven’t been able to find clear guidance on whether I should use swap or not on my machine, and if so, how much to set. There are some arguments in favor of using swap that I’ve not been able to fully understand, essentially saying that swap lets you have more useful pages in memory because less useful pages can be swapped out.

But what does that really mean? If I have 20 GB of RAM and 4 GB of swap on a hard drive, isn’t that worse than just adding another 4 GB of RAM and having no swap? And if I have 24 GB of RAM, are you going to tell me I still need swap?

My total RAM + swap will forever be finite, and at some point I will run out of memory. And if I’m to believe that having swap is “a method of increasing equality of reclamation”, what if I put my swap in RAM? That sounds silly, but if simply adding 4GB more RAM is not better than having 4GB more swap, then isn’t that true?

So where are the benchmarks? And how would you even benchmark something like this? My use case as a desktop user is certainly different from a constantly loaded server, but here are the very real downsides to me using swap:

  • Hard drive wear is real. Flash memory wears down, and spinning disks cannot rest if they are constantly asked for swap.
  • The out-of-memory (OOM) case is ridiculously painful. Without swap, the offending process (usually a runaway program) gets killed. With swap, my entire computer grinds to a halt and becomes unresponsive until the offending process has managed to fill up the swap as well. That could take 10 minutes or longer.

Turning off swap means that any process that uses too much memory is immediately killed. That sounds bad except it would have gotten killed even if I did have swap, just much later.

And after turning off swap, I haven’t noticed any performance hits at all. In fact, performance has been better because idle processes don’t get pushed into swap during times of high memory pressure, so I don’t need to incur the penalty of swapping them back in (the infamous alt-tab-is-slow-because-of-swap problem).

If you’re still using swap on your desktop machine out of superstition, try turning it off. Maybe you don’t need it.

Cleaning up narration audio in Audacity

The below steps have worked great for me for cleaning up narration audio I’ve recorded. After running through this process, the audio should be ready to be assembled into a video, podcasts, etc.

When recording:

  • Maintain consistent distance between mouth and microphone
  • Don’t record too loud (past maximum) or too soft (too close to noise floor)
  • Leave 5 seconds of silence before start for noise reduction

After recording:

  • Noise Reduction using first five seconds of silence
  • Compressor
    • Threshold: -12 dB
    • Noise Floor: -25 dB
    • Ratio: 5:1
    • Attack Time: 0.10 secs
    • Release Time: 1.0 secs
    • enable “Compress based on Peaks”
  • De-Clicker:
    • default parameters
    • optional – listen to audio first
  • Normalize
    • Normalize Peak Amplitude: -1 dB
    • Do separately for each section if levels inconsistent

These steps will fix noise and levels, giving a good baseline for then incorporating into a video or presentation.

Why Engineers Hate Scrum

There are a lot of people out there burned by bad experiences with Agile and Scrum. And talking to them about it, what their team called “Agile” consisted of doing things that Scrum says to do without understanding why they’re done and consequently not getting benefits out of it.

(This reminds me of the legendary “cargo cults” – supposed Pacific Islanders who, after having their world transformed by WWII, constructed dirt runways and wooden planes in hopes of attracting cargo drops. All of the ritual, none of the benefits.)

I don’t have strong feelings about Scrum. If your team is doing well without it, great. If you’re using it successfully, great. If you’re using it “Just Because” and everyone on the team hates it, then something got lost in translation.

Here are some common Scrum mistakes:


The biggest interview fail I ever tried was the take-home problem

Hiring was always one of the biggest challenges in running DoubleMap. Our limited budget, small team, and geographic location put a lot of constraints on hiring, and I was always interested in learning about how other companies interviewed candidates.

Around 2013/2014-ish, I read about companies giving candidates take-home coding problems and decided to try it out. The basic idea was that a take-home problem offered candidates more time flexibility because it’s async and is a better simulation of the work compared to whiteboard questions.

I liked the idea, so I cooked up two take-home problems that candidates could choose from: a front-end problem where you were asked to write some HTML/JS to talk to a JSON API, and a back-end problem where you were asked to create some JSON API endpoints given a database.


Optimizing Python With Cython

This is a mirror of a 2015 article I wrote about using Cython.

Python is slow. It’s still faster than a lot of other languages, but describing Python code as “fast” will probably get some weird looks from people who use compiled or JITted languages.

Still, I love Python for its design and speed at which I can write good code. Most of the time, when writing Python for distributed or web applications, it doesn’t matter. Your runtime is usually dominated by network traffic or data accesses than it is by raw computational speed. In those cases, the correct optimization is often reducing network requests or indexing your data.

Sometimes—just occasionally—we actually do need fast computation. Traditional Python wisdom has been to profile your code (perhaps using cProfile), identify the hot spot, and rewrite it in C as a Python extension. I’ve had to do that before for a handwriting recognition algorithm with great success (it turned a 2–3 second computation into a practically real-time one), but writing C code and then figuring out the FFI was a pain. I had to rewrite the algorithm in C, verify that my C version was correct and didn’t have memory errors, and then figure out the right incantations to get it to cooperate with Python.

Let’s try something different this time.


At DoubleMap, geographic distance calculation is something that gets used a lot, and we use the haversine function to calculate distance between two points on Earth. In some of our data analysis, we might run the haversine function millions of times in a tight loop, and it was causing some reports to take way too long.


NFTs are tearing the art community apart

Currently, the collision of artists and cryptocurrencies is playing out across Twitter and other platforms, dividing artists who normally praise and support each other into two camps. One camp wants to make money selling their art as NFTs, and the other camp hates the idea.

If you haven’t heard already, NFTs are enjoying increasing popularity alongside the current record-high cryptocurrency values. NFT stands for Non-Fungible Token – an atomic digital token that can be traded for cryptocurrency. Many artists are now selling “digital editions” of their artwork as NFTs on special-purpose marketplaces – the buyer can call themselves the owner of a piece of digital art, similar to how one might purchase a limited edition print. Some of these art NFTs have been trading for the equivalent of tens of thousands of dollars, despite granting the buyer no physical copy, no reproduction rights, or anything other than the ability to say, “Yes, I own this token,” and to sell it to someone else.

What’s been particularly divisive is the objection to the shockingly high energy usage of the most popular cryptocurrencies. One estimate puts the energy consumption involved in a single NFT sale at around 340 kWh, equivalent to driving 1,000km in an ICE car. Most of this is due to the proof-of-work algorithm that’s core to Ethereum[1], the blockchain that most of these NFT sales take place on. Essentially, many objectors see making and selling NFTs as something akin to clearcutting rainforests in order to grow crops.


Customize Huion tablet buttons on Linux

The “Huion Kamvas Pro (2019)” comes with 16 physical buttons and two touch strips along the sides of the tablet. On Windows, you can configure the buttons to send custom keystrokes (e.g. toggling painting tools or changing brush size). However, the key mapping is mirrored from the left to the right side, so you can only have 10 distinct assignments.

On Linux, the pen works out-of-the-box on my Ubuntu machines, but there’s no good way to customize the buttons. You can force X to use the wacom drivers (xf86-input-wacom) which lets you use xsetwacom to customize some of the buttons, but the touch strips don’t seem to be supported, nor do buttons 13 through 16 (the bottom right buttons).

I have published a Python program that watches the raw data coming from the tablet and sends commands using xdotool. It currently has a few rough edges: setting it up requires a bit of compilation in order to link to libxdo, and you also need to have permission to read from the /dev/hidraw device which you don’t have by default. For the permissions, I’m sure someone more knowledgeable can suggest a better way to set things up, but you can run the program as root or a setuid program, or manually grant read access on the /dev/hidraw file.

Configuring it should be pretty easy. Here are some buttons that I have configured for use with Krita, for example:


Using TypeScript to check for missing cases

TL;DR: use your type system to keep you from forgetting to handle all cases in switch statements and object keys.

Often in programming, you have to deal with a list of distinct options. In some languages, this would be expressed by an enum, but in TypeScript it’s more common to express them as specific strings:

type CarType = “mazda-miata” | “honda-s2000” | “toyota-mr2” | “pontiac-solstice”;

(Let’s say we’re building a racing game featuring old roadsters in this contrived example…)

Rather than enumerate all of the types in one CarType declaration, we might have varying properties for each car, expressed as TypeScript discriminated unions: