Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
30
A project I'm working on (which is definitely not my SIGBOVIK submission for this year, and definitely not about computer ergonomics) requires me to use MIDI. And to do custom handling of it. So I need something that receives those MIDI events and handles them. But... I'm going to make mistakes along the way, and a terminal program isn't very interesting for a presentation. So of course, this program also needs a UI. This should be simple, right? Just a little UI to show things as they come in, should be easy, yeah? Hahahaha. Haha. Ha. Ha. Whoops. The initial plan Who am I kidding? There was no plan. I sat down with egui's docs open in a tab and started just writing a UI. After a few false starts with this—it turns out, sitting down without a plan is a recipe for not doing anything at all—I finally asked some friends to talk through it. I had two short meetings with two different friends. Talking it through with them forced me to figure out ahead of time what I wanted, which made the...
2 weeks ago

More from ntietz.com blog - technically a blog

Bright lights in dark times

It's kind of dark times right now. And I'm definitely only talking about the days being short. It's pretty dark out right now, since it's the winter in the northern hemisphere. Every year, I start to realize somewhere around January that I'm tired, really tired, and don't want to do anything. I'm not sure that this is seasonal affective disorder, but I did start to explore it and found that bright light makes me feel a lot better in the winter. The problem is, I go through this discovery every year, it seems. My earlier attempts After I first learned that bright light can actually help you feel better in the winter, I got a Happy Light. That's the actual branding on it, and it might have helped, maybe? But it was really inconvenient to use. I got one of the more affordable ones, which meant a small panel I had to sit uncomfortably close to. And I was supposed to use it almost first thing in the morning for 30 minutes. That's... really not going to fit in my routine, especially now that I have two young kids who are raring to go right at 7am, and they do not want to deal with mom sitting in front of a lamp that early. So I just kind of stopped using it, and would poke at it in the drawer occasionally but not really do anything with it. Instead, I got a few more lamps in my office. These seemed to help, a little, but that might just be that I like my spaces well lit1. Going as bright as possible Somewhere along the line I saw a post talking about winter energy and light. And the author suggested that indoor lights just have to be bright. They recommended some high-watt LED bulbs. I went ahead and ordered some and... these are very bright. They're 100 watts of LEDs each, and I have two of them. I plugged them in to these plug-in fixtures that have inline switches. These seemed to really help me out a lot. On days I used them, I was more energetic and felt better. But I had a lot of days when I didn't use them, because using them was inconvenient. The main problems were that I had to turn on two switches manually, and they were too bright to look at. Turning them on doesn't sound like a lot, but doing it every day when you're already struggling can be just enough friction to avoid doing it! The brightness was the biggest issue, because they were blinding to look at and cast this reverse shadow out of my office. The brightness was the point, so now do I deal with that? Shading it Turning on the switches was easy. I put them on some smart outlets, set a schedule, and now they turn on and off at my predetermined times! Then I had to make a shade for the lamps. My main goal here is to diffuse the light a bit, so I was thinking of a small wooden frame wrapped in a light white fabric. That would spread the light out and avoid having any spots that are too bright to look at. I took measurements for where it was going to fit, then headed out to my workshop. Then I ripped a scrap board into 0.75"x0.75" stock. I ended up with just enough for the design. This is what I ended up with, after it was glued up. Here's a bonus detail shot of the extremely unnecessary joinery here. But hey, this joinery was incredibly satisfying, and the whole thing went together without any nails or screws. Joy makes it worthwhile. And then with the fabric attached2, it ended looking pretty nice. In its intended home, you can see that it does its job! It's now not painful to look straight at the lamp. You wouldn't want to do it, but it won't hurt you at least! Waiting for summer Of course, we need these bright lights during the winter, during the dark days. But summer will come again. Not all of us will see the long days. We'll lose some to the winter. But summer will come again, and we'll be able to go outside freely. Soak in the sun. Be ourselves. Dance in sunshowers. It's hard to see that in the darkest of times. I'm writing this for myself as much as I'm writing it for you. But believe me: you are strong, and I am strong, and together, we will survive. It seems like this winter is going to last a long time. And it probably will. But I'm not just talking about the winter, and the dark times are going to end, eventually. 1 Most places are very poorly lit and don't have enough lamps. You should have a lot of lamps in every room to make it as bright and cheery as possible! I refuse to believe otherwise, but my wife, a gremlin of the darkness3, does like rooms quite dim. 2 My wife helped with this step. She works with fabric a lot, and this was our first project combining our respective crafts. Certainly not our last! 3 She approved this phrasing.

6 days ago 9 votes
My writing process, and how I keep it sustainable

Recently, a reader wrote to me and asked about my writing process and burnout. They had an image in their head that I could sit down at a computer and type up a full post on a given topic, but were unsure if that's the right approach when they start blogging. And they were concerned about how to keep things sustainable for themselves, too. I started to write back to them, but decided to ship my answer for everyone else, as well. Now, to be clear: this is my writing process. There are many other very valid approaches, such as what Gabriella wrote. But I think outlining my process here will at least help break some of the mystique around writing, because I certainly do not just sit down and plunk out a post. Well, it often looks like that if you're an outside observer1. But that's only what's visible. How I write The very first piece of any blog post for me is an idea, some inspiration, something I want to share. These ideas come from a variety of places. Some of them are from conversations with friends or coworkers. Others come from problems I'm solving in my own code. Or they come from things I'm reading. Very rarely, they come from just thinking about things, but those are other sources with the connection hidden by the passage of time. After an idea comes to me, I try to capture it quickly. If I don't, it's probably gone! I store these ideas in a note in Obsidian, which currently has 162 ideas in it2. I format this file as a bulleted list, with minimal categorization so that the friction for adding something new is as low as possible. When I revisit this list to plan things to write (I do this every few weeks), I'll remove things which are either completed or which I'm confident I am not interested in anymore. Ideas which are promoted from "want to write" to "definitely going to write" then get moved into my primary task tracking system, LunaTask. When I start typing an article, I move it from "later" or "next" to "in progress". But in reality, I start writing an article far before I start typing it. See, the thing is, I'm almost always thinking about topics I'm going to write about. Maybe this is an element of having ADHD, or maybe it's from my obsessive interests and my deep diving into things I'm curious about. But it started happening more when I started writing on a schedule. By writing on a schedule, having to publish weekly, I've gotten in the habit of thinking about things so that I can hit the ground running when I sit down to type. Once I've picked out an idea, then it sits in the back of my head and some background processing happens. Usually I'm not crystal clear on what I want to say on a topic, so that background process churns through it. After figuring out what I want to say, I move onto a little more active thought on how I want to say it and structure it. This happens while I'm on a run or a walk, while I'm on coffee breaks, between exercises I'm working on for my music lessons. There's a lot of background processing between snatches of active effort. Piece by piece, it crystallizes in my mind and I figure out what I want to say and how to say it. Then, finally, I sit at the keyboard. And this is where it really looks like I just sit down and write posts in one quick sitting. That's because by then, the ideas are fully formed and it's a matter of writing the words out—and after a few years of that, it's something I got pretty quick at. Take this blog post for example. I received the initial email on January 1st, and it sat in my inbox for a few days before I could read it and digest it. I started to write a reply, but realized it would work as a blog post. So it ended up in background processing for a week, then I started actively thinking about what I wanted to write. At that point, I created a blog post file with section headings as an outline. This post ended up being delayed a bit, since I had to practice content-driven development for a project that has a self-imposed deadline. But the core writing (though not the typing) was done in the week after opening the original email. And then when I finally sat down to write it, it was two 20-minute sessions of typing out the words3. Editing and publishing After the first draft of a post is written, there's a lot of detail work remaining. This draft is usually in the approximate shape of the final post, but it may be missing sections or have too much in some. I'll take this time to do a quick read over it by myself. Here I'm not checking for spelling errors, but checking if the structure is good and if it needs major work, or just copy edits. If I'm unsure of some aspects of a post, I ask for feedback. I'll reach out to one or two friends who I think would have useful feedback and would enjoy reading it early, and ask them if they can read it. This request comes with specific feedback requests, since those are easier to fulfill then a broad "what do you think?" These are something along the lines of "was it clear? what parts stuck out to you positively or negatively? is there anything you found unclear or inaccurate?" Once people get me feedback, I add an acknowledgement for them (with explicit consent for if/how to share their name and where to link). After I'm more sure of the structure and content of a post, I'll go through and do copy edits. I read through it, with a spellchecker enabled4, and fix any misspellings and improve the wording of sentences. Then it's just the final details. I write up the blurb that goes in my newsletter and on social media (Mastodon, Bluesky, and LinkedIn, currently). I make sure the date is correct, the title is a decent one, and the slug mostly matches the title. And I make sure I did do things like acknowledgements and spellchecking. Here's the full checklist I use for every post before publishing. - [ ] Edited? - [ ] Newsletter blurb written? - [ ] Spellchecked? - [ ] Is the date correct? - [ ] Is the title updated? - [ ] Does the slug match the title? - [ ] Are tags set? - [ ] Draft setting removed? - [ ] Are acknowledgements added? Once that checklist is complete, it's ready to go! That means that on Monday morning, I just have to run make deploy and it'll build and push out the article. Making it sustainable I've been writing at least one post a week since September 2022—over two years at this point, and about 200,000 words. I've avoided burning out on it, so far. That's an interesting thing, since I've burned out on personal projects in the past. What makes this different? One aspect is that it's something that's very enjoyable for me. I like writing, and the process of it. I've always liked writing, and having an outlet for it is highly motivating for me. I thrive in structure, so the self-imposed structure of publishing every week is helpful. This structure makes me abandon perfection and find smaller pieces that I can publish in a shorter amount of time, instead of clinging to a big idea and shipping all of it at once, or never. This is where content-driven development comes in for me, and the consistent schedule also leads me to create inspiration. I also don't put pressure on myself besides the deadline. That's the only immovable thing. Everything else—length, content, even quality5—is flexible. Some of my earliest posts from 2022 are of the form "here's what I did this week!" which got me into a consistent rhythm. The most important thing for me is to keep it fun. If I'm not finding something fun, I just move on to a different post or a different topic. I have some posts that would be good but have sat in my backlog for over a year, waiting to be written. Whenever I sit down to write those, my brain decides to find a bunch of other, better, posts to write instead! So far, it's been fun and motivating to write weekly. If that ever stops, and it becomes a job and something I dread, I'll change the format to be something that works better for me instead! 1 If this is what it looks like then hey, stop peeking through my window. 2 I keep them in a bulleted list, each starting with -, so I can find the count via: grep "\s*-" 'Blog post ideas.md' | wc -l 3 Drafting documents is where I feel like my typing speed is actually a real benefit. On a typing test this evening, I got 99% accuracy at 120wpm. This lets me get through a lot of words quickly. This is a big plus for me both as a writer and as a principal engineer. 4 I leave my spellchecker disabled the vast majority of the time, since the squiggles are distracting! It's not a big deal to misspell things most of the time, and I'd rather be able to focus on getting words out than noticing each mistake as I make it—talk about discouraging! 5 That said, I think it's hard to judge your own quality in the moment. As writers, we can be very critical of ourselves. We think we wrote something boring but others find it interesting. Or something we think is quite clever, others find uninspiring. Sometimes, when I push out something I think is lazy and quick and low-quality, I'm surprised to find a very eager reception.

a week ago 29 votes
Great things about Rust that aren't just performance

Nearly every line of code I write for fun is in Rust. It's not because I need great performance, though that's a nice benefit. I write a lot of Rust because it's a joy to write code in. There is so much else to love about Rust beyond going fast without segfaults. Here are a few of my favorite things about it. Note that these are not unique to Rust by any stretch! Other languages have similar combinations of features. Expressive type safety There are two aspects of Rust's type system that I really enjoy: type safety and expressiveness. I got a taste of this expressiveness back when I learned Haskell, and had been seeking it. I found it in Rust. One of the other languages I use a fair amount at work1 is Go, and its type system is much harder for me to express ideas in. You can do it, but you're not getting the type system's help. Rust lets you put your design straight into types, with enums and structs and traits giving you a lot of room to maneuver. All the while, it's also giving you good type safety! I can express a lot in Python, but I don't trust the code as much without robust tests. You don't have a compiler checking your work! It's remarkably helpful having Rust's compiler by your side, making sure that you're using types correctly and satisfying constraints on things. To call back to data races, the type system is one of the reasons we can prevent those! There are traits that tell you whether or not data is safe to send to another thread or to share with another thread. If your language doesn't have the equivalent of these traits, then you're probably relying on the programmer to ensure those properties! That said, Rust's type system isn't an unmitigated good for me. It can take longer to get something up and running in Rust than in Python, for example, because of the rigidity of the type system: satisfy it or you don't run. And I find a lot of Rust that uses generics is very hard to read, feeling like it is a soup of traits. What we make generic is an implementation question, and a cultural question, so that isn't necessarily inherent to the language but does come strongly bundled to it. It doesn't crash out as much Okay, I have a beef with Go. They included Tony Hoare's "billion-dollar mistake": null pointers. Go gives you pointers, and they can be null2! This means that you can try to invoke methods on a null pointer, which can crash your program. In contrast, Rust tries very very hard to make you never crash. You can make null pointers, but you have to use unsafe and if you do that, well, you're taking on the risk. If you have something which is nullable, you'd use an Option of it and then the type system will make sure you handle both cases. The places you typically see crashes in Rust are when someone either intentionally panics, for an unrecoverable error, or when they unintentionally panic, if they use unwrap on an Option or Result. It's better to handle the other case explicitly. Fortunately, you can configure the linter, clippy, to deny code that uses unwrap (or expect)! If you add this to your Cargo.toml file, it will reject any code which uses unwrap. [lints.clippy] unwrap_used = "deny" Data race resistance It's so hard to write concurrent code that works correctly. Data races are one of the biggest factors contributing to this. Rust's data race prevention is an incredible help for writing concurrent code. Rust isn't immune to data races, but you have to work harder to make one happen. They're almost trivial to introduce in most languages, but in Rust, it's a lot harder! This happens because of the borrow checker, so it's harder to have multiple concurrent actors racing on the same data. You get more control, when you want With Rust, you know a lot more about what the CPU and memory will be doing than in many other languages. You can know this with C and C++, and newer systems programming languages like Zig. Rust is a little unique, to me, in being notably higher level than these languages while giving you ultimately the same amount of control (if you break glass enough, for some things). You're still subject to the operating system, most of the time, so you can't control the CPU and memory fully, but you get a lot more control than in Python. Or even than Go, another language used when you need good performance. This lets you predict what your code is going to do. You're not going to have surprise pauses for the garbage collector, and you're not going to have the runtime scheduler put some tasks off for a while. Instead, you know (or can determine) when memory will be deallocated. And you ultimately control when threads take tasks (though with async and the Tokio runtime, this gets much muddier and you do lose some of this control). This predictability is really nice. It's useful in production, but it's also just really pleasant and comforting. Mixing functional and imperative Rust lets you write in a functional programming style, and it also lets you write in an imperative programming style. Most idiomatic code tends toward functional style, but there's a lot of code that uses imperative style effectively as well! This is pretty unique in my experience, and I really like it. It means that I, the programmer, can pick the paradigm that best fits the problem at hand at any given moment. Code can be more expressive and clearer for the author and the team working on the code. One of the cool things here, too, is that the two paradigms effectively translate between each other! If you use iterators in Rust, they convert into the same compiled binary as the imperative code. You often don't lose any efficiency from using either approach, and you're truly free to express yourself! Helpful compiler errors A few other languages are renowned for their error message quality—Elm comes to mind. Rust is a standout here, as well. Earlier in my career, I was abused by C++. Besides all the production crashes and the associated stress of that, the compiler errors for it were absolutely inscrutable. If you messed up a template, you'd sometimes get thousands of lines of errors—from one missing semicolon. And those wouldn't tell you what the error was, but rather, what came after the mistake. In contrast, Rust's compiler error messages are usually pretty good at telling you exactly what the error is. They even provide suggestions for how to fix it, and where to read more about the error. Sometimes you get into a funny loop with these, where following the compiler's suggestions will lead to a loop of suggesting you change it another way and never get it to succeed, but that's fine. The fact that they're often useful is remarkable! It's fun! This is the big one for me. It's very subjective! I really like Rust for all the reasons listed above, and many more I've forgotten. There are certainly painful times, and learning to love the borrow checker is a process (but one made faster if you've been abused by C++ before). But on balance, using Rust has been great. It's fun having the ability to go ripping fast even when you don't need to. It's lovely having a type system that lets you express yourself (even if it lets you express yourself too much sometimes). The tooling is a joy. All around, there's so much to love about Rust. The performance and safety are great, but they're the tip of the iceberg, and it's a language worth considering even when you don't need top performance. 1 Go was introduced at work because I advocated for it! I'd probably use Go for fun sometimes if I were not getting enough of it at my day job. It's a remarkably useful language, just not my favorite type system. 2 They're expressed as nil in Go, which is the zero value, and is the equivalent of null elsewhere.

3 weeks ago 43 votes
Reflecting on 2024, preparing for 2025

If you do things a few times, they're a tradition. This is the third time I'm writing one of these, so I guess it's an annual tradition now! This is where I reflect on the year that's been, and talk some about my hopes and goals for the next year. Reflecting on 2024 This year has been a lot, and there are a few months of it that just feel like a black hole to me. That's because I got sick in the middle of it. I'm really proud of how much I got done in spite of being the sickest I've ever been. And I'm excited to see what I can do next year, now that I'm nearly fully recovered. Professional I spoke at a conference! This year marked my first ever conference talk. Technically, my first one was at SIGBOVIK 2024, but I'm really talking about !!con. I've submitted talks to conferences before and this is the first one I've ever had accepted. You can watch the recording. That link takes you to the playlist of all !!con talks from this (final) year, so please enjoy them all! It was an incredible experience. The whole conference felt like I was with old friends who I just hadn't met yet. It made me remember the power of connecting with other nerds in physical space. And it reminded me of the joy of being on a stage. More on that in the personal section. I wrote even more than last year. My goal for this year was to continue my status quo: publish at least one blog post each week. I overshot this again, with 60 blog posts over 90,000 words. The most important thing for me has been consistency. By writing every week, I've been able to continue to use this momentum to stretch my creative practice. This even held during my illness this summer, and it was something for me to hold on to when I could do little else. I got paid for my writing. This is the first time I have been paid explicitly to write. I was sponsored to write a post about an open-source product (and the contract even requires that I misuse it, on purpose, since that's what I pitched). The overall experience was pretty good, and I'm also not sure I would do it again in the near future: while I'm working a day job, I don't want to spend my limited writing time on things I'm not already self-motivated to publish. But—I dearly want to find a way to get paid for my writing which isn't sponsored posts. This might look like a Patreon or similar, so let me know if you're interested, and you might be the nudge to get me over that finish line. I started coaching people. This year saw me take on my first three coaching clients. These were all pro bono, friends of mine who needed some help with career questions and technical leadership development. It's been an incredible experience, getting to directly help people grow and overcome their challenges. (If you're interested in being coached by me on technical leadership, reach out to me! You're a particularly good fit if you're a senior or staff software engineer aiming to level up, and members of marginalized groups are who I'm most hoping to help.) I grew as a leader. Most of my reflection here is private because it's so intertwined with specific leadership challenges at my day job, but it's been a really helpful year for me in my leadership development. I've learned a lot, and I've seen a lot of old decisions come around to their conclusion to complete my learning arc. Personal I got sick. It's hard to say when exactly I got sick, but my symptoms got to where I had to go to my doctor at the start of May, and my full recovery started in November. I am pretty sure I was sick before that, since I had started needing more and more sleep throughout the spring, but it's impossible to say at this point. At the peak of my illness, I was in near constant pain (about a 6 on the pain scale) and could not stand up for 5 minutes without having tachycardia. If that happened, I had to lie on my back until my heart rate came back down. Walking around the block was an impossibility, when months prior I was running 25 miles a week and hauling the kids around on my bicycle. I bounced through multiple doctors. We hit the end of my general practitioner's expertise, so she referred me to a GI practice since our core symptom was related to my liver and abdominal pain. (The liver was ultimately a red herring: what looked unusual on my ultrasound was, with further testing, not concerning.) The GI practice did a lot of tests, and I had a lot of waiting (GI docs are in such high demand, you can't see them), but ultimately... they also found nothing. Meanwhile I was still in pain, and could do very little. Around this point, I went on medical leave with my employer. Before that I had been working as much as I was able, and contributing something of value, but it had become clear that I was going to need to focus on my recovery if I wanted to actually get to the bottom of this. Going on medical leave was terrifying, because it meant I would be without income for months. But it was ultimately the right decision. Around this same time, I went to my third doctor. She got me a diagnosis. A friend sent me to her, since she's a specialist in conditions that present with ambiguous symptoms and chronic fatigue. This doctor ran a lot of tests—expensive tests, which insurance doesn't cover—and we ultimately got me a firm diagnosis six months into the whole ordeal. After starting treatment, it's been a very fast recovery: almost two months in, and I'm over 90% normal. How our definitions of "fast" shift. I may have relapses in the future, there's no way to know, but I'm relieved to know what is going on. I stopped running. The pain started in May when I was running, so I had to abruptly go from running 25 miles a week to not at all. Eventually it moved from pain while running to constant pain. And then eventually... it faded away entirely, as long as I keep up on one of my medications. Now I can run if I want to—but I've decided not to. For the last decade, I've identified as a runner. I did a few half marathons with reasonable times, and I did a marathon in 4:07, finishing in 75 F heat. For much of the decade before that, I identified as a cyclist. I was into cycling from the moment I could get on a bike, always wanting to go further and faster. In high school, I did a 100 mile bike ride. Now that I've been forced to take a break from it, I've realized I'm pretty content with not putting in the grueling schedule needed to get back to the high level of performance I thought I wanted to target next year. I might get back into this sooner or later, but right now, I'm working on functional strength and being healthy and having more balance in my life. I got back into music! When I was sick, I bought a wind synth and started playing music again. Then I started taking lessons with an incredible teacher who's an accomplished musician in his own right. And then I got a drum pad, another wind synth, a hand-me-down keyboard, and got my clarinet back out. I've fallen deep into this and I'm loving every single minute of it, frustration and all. This is replacing a lot of the dedication and discipline I used to get from endurance exercise. It has the added benefit of creating art in the process, which heals my soul. The deep breathing involved in playing wind instruments certainly helps me as well. I'm learning some music theory, and it's hard! I want to learn how to write songs and compose music, and I'm going to get there. If you have any favorite resources for this, please send them to me! The upshot of getting back into music is that it will, hopefully, give me a way to perform again. I've been a performer in some aspect for a lot of my life. In school, I was in our concert bands and in small wind ensembles. I was on the debate team, a shock to people who knew me as the shy kid who shook when forced to speak in front of the class. Since school, though, I've lost this opportunity. I got a taste of performance again with my conference talk, and I think music will be my route to performing regularly. I'm starting to use my voice. Advocacy and activism were always things I looked up to but didn't feel like I was able to do. But then I found I have a voice, and I realized I need to use it. I shared a few posts this year on things that are important and required me to speak up, like trans rights and the crisis that was happening in Asheville. Organized two rated chess tournaments. This year I organized two tournaments for our local chess club! They had about 12 players each, and went off smoothly. It was a good experience all around. I'm not sure I'll have the opportunity (or desire) to do this again this year. Kept my head up. Current events have been... a lot... and I've managed to keep my head up for most of it. I'm going to keep going, and keep trying. Upgraded the workshop for all-year use. Historically I've only been able to use my workshop for a few months of the year when the weather is right. This year, we got it insulated, replaced the windows, and added a heat pump, and all those combine to mean I can keep it temperate all year so I can go out there whenever I want. (Some wood finishes are inadvisable in cold or hot weather since they need good ventilation, but otherwise any time for anything.) It's changed my relationship with woodworking, since now I can pop out there and make something whenever I want. When I was rearranging my desk yesterday, I realized I really needed a headphone stand. And so I popped out to the workshop and put in a total of two hours of work (split across a few sessions for glue and finish to dry), and now I have one! Something similar happened when I needed an adapter to mount my drum pad: I just made it. Last year's goals This year was a lot! How does it stack up against what I wanted to do last year? ❓ I wanted to keep my rights, and I did—for now, in my own state. But it's really tenuous, and there are many states where I'd be punished for using a bathroom. The state I was born in, Ohio, has banned trans folks from using the bathrooms consistent with their gender, and the incoming administration is a dark cloud. I won't call this a miss yet, but I can't call it a win. ✅ No personal side projects went into production! I once again toyed with the idea and once again talked myself out of it. Good job, me! ✅ I am not sure I struck a better balance with calls and making, but I embraced that I love talking to my friends and just continued to make time for them. This is going to be even more important next year. ✅ I kept writing on the same schedule, and I did expand it! I did a creative writing class, and even wrote a poem as well. ❌ I did not do any comedy this year, so it's a miss. But it's a happy miss, because I found other things that I was drawn to. ✅ I stayed pretty active in my communities, given my health. ✅ I was a good parent and partner, given my health. ✅ I finished voice training! This was almost a gimme, since I was done in January this year. ✅ My ergonomic setup was definitely improved. I still want to work on using Talon more, but I have made improvements there as well. ❌ I did not do more technical projects this year. I've started a few, and I'm going along in the background, but health got in the way. ❌ I did not get back into competitive chess, and I probably won't. It simply doesn't feel as important right now. Music is filling the role that it filled in my life. ✅ I kept my mental health strong! I think I did really well on these goals, even if it were a normal year and not a year where I spent 6 months sick to various degrees and where I started to feel the crushing weight of our politics and its threat on my rights and my life. This year I did learn a bit about what is important to me, and where I want to spend my time. That's reflect in my hopes and goals for next year. Hopes and goals for 2025 These aren't predictions or concrete goals, but a reflection on what I'd like the next year to be. This is what I hope 2025 looks like for me. Keep my rights. A perennial goal at this point, it's the headliner since trans rights are near the top of the Republican's agenda for this new administration. I'm wary, and I'm going to do what I need to do to keep myself and my family safe. I think that can be done from where I am in a safe, supportive community, but we will keep ourselves safe while continuing to advocate for all those who need protection. In particular, I'm going to keep living my best life and being positive representation for other trans folks who are similarly under attack. No personal-time side projects into production. This one will probably be a forever anti-goal for me. I don't want to do ops-y things in my free time (despite feeling like shaving that yak occasionally), and I don't want to support a product in my free time. My free time is more about playful exploration. Maintain relationships with friends and family. This is the 2025 version of 2024's goal of "strike a better balance with calls and making." I'm positive I'll have time set aside for making things and for playing music. But this is going to be a challenging year, so my loved ones (given/chosen family and dear friends) will need my support and I will need theirs. So I'm going to put those relationships first and foremost. Explore ways to make this my living. I want to do more playful exploration, the kinds of things I do on my blog, and make that my living eventually! 2025 isn't when I'll get there, but I want to try out one or two things (like a Patreon? fund myself via consulting and coaching?) to start understanding what might work for both me and my readers. Keep my mental health strong. This is going to be a challenge, and I am in a good spot for it. I'll need to dedicate effort to it, though, what with the upcoming onslaught. Release some recorded music. I'm working on a lot of aspects of my music. Eventually, I want to write something and release it. This year might be releasing a recording of a cover, but it might also be an original piece. I'm not sure! Write some original music. I don't know if I'll release something of my own, but I know I need to work on it. We'll see how this goes! It's scary to me, and it's also something I'm confident I can do if I put in the effort to learn how. Do some ridiculous fun projects with code. There are a few things I really wanted to work on in 2024 that are just playful, fun, ridiculous things. I didn't get to do them since I was, uh, kinda sick (not sure if I mentioned that yet). So they're burning to get out of me this year. I want at least one to get out of me this year. * * * That's it! I've poured a lot of myself into this post. If you've made it this far: thank you, so much, for reading. 2024 had a lot in it, good and bad. I'm trying to hold both at the same time, to remember the good and remember the bad, as they are both important aspects of the year for different reasons. I hope that 2025 keeps much of the good, and that we can minimize the bad. I'm going to do everything I can to hold joy in this world. Please join me in that, and let's fill 2025 with joy, even in the face of all that's being thrown at us.

a month ago 38 votes

More in programming

Adding auto-generated cover images to EPUBs downloaded from AO3

I was chatting with a friend recently, and she mentioned an annoyance when reading fanfiction on her iPad. She downloads fic from AO3 as EPUB files, and reads it in the Kindle app – but the files don’t have a cover image, and so the preview thumbnails aren’t very readable: She’s downloaded several hundred stories, and these thumbnails make it difficult to find things in the app’s “collections” view. This felt like a solvable problem. There are tools to add cover images to EPUB files, if you already have the image. The EPUB file embeds some key metadata, like the title and author. What if you had a tool that could extract that metadata, auto-generate an image, and use it as the cover? So I built that. It’s a small site where you upload EPUB files you’ve downloaded from AO3, the site generates a cover image based on the metadata, and it gives you an updated EPUB to download. The new covers show the title and author in large text on a coloured background, so they’re much easier to browse in the Kindle app: If you’d find this helpful, you can use it at alexwlchan.net/my-tools/add-cover-to-ao3-epubs/ Otherwise, I’m going to explain how it works, and what I learnt from building it. There are three steps to this process: Open the existing EPUB to get the title and author Generate an image based on that metadata Modify the EPUB to insert the new cover image Let’s go through them in turn. Open the existing EPUB I’ve not worked with EPUB before, and I don’t know much about it. My first instinct was to look for Python EPUB libraries on PyPI, but there was nothing appealing. The results were either very specific tools (convert EPUB to/from format X) or very unmaintained (the top result was last updated in April 2014). I decied to try writing my own code to manipulate EPUBs, rather than using somebody else’s library. I had a vague memory that EPUB files are zips, so I changed the extension from .epub to .zip and tried unzipping one – and it turns out that yes, it is a zip file, and the internal structure is fairly simple. I found a file called content.opf which contains metadata as XML, including the title and author I’m looking for: <?xml version='1.0' encoding='utf-8'?> <package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="uuid_id"> <metadata xmlns:opf="http://www.idpf.org/2007/opf" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata"> <dc:title>Operation Cameo</dc:title> <meta name="calibre:timestamp" content="2025-01-25T18:01:43.253715+00:00"/> <dc:language>en</dc:language> <dc:creator opf:file-as="alexwlchan" opf:role="aut">alexwlchan</dc:creator> <dc:identifier id="uuid_id" opf:scheme="uuid">13385d97-35a1-4e72-830b-9757916d38a7</dc:identifier> <meta name="calibre:title_sort" content="operation cameo"/> <dc:description><p>Some unusual orders arrive at Operation Mincemeat HQ.</p></dc:description> <dc:publisher>Archive of Our Own</dc:publisher> <dc:subject>Fanworks</dc:subject> <dc:subject>General Audiences</dc:subject> <dc:subject>Operation Mincemeat: A New Musical - SpitLip</dc:subject> <dc:subject>No Archive Warnings Apply</dc:subject> <dc:date>2023-12-14T00:00:00+00:00</dc:date> </metadata> … That dc: prefix was instantly familiar from my time working at Wellcome Collection – this is Dublin Core, a standard set of metadata fields used to describe books and other objects. I’m unsurprised to see it in an EPUB; this is exactly how I’d expect it to be used. I found an article that explains the structure of an EPUB file, which told me that I can find the content.opf file by looking at the root-path element inside the mandatory META-INF/container.xml file which is every EPUB. I wrote some code to find the content.opf file, then a few XPath expressions to extract the key fields, and I had the metadata I needed. Generate a cover image I sketched a simple cover design which shows the title and author. I wrote the first version of the drawing code in Pillow, because that’s what I’m familiar with. It was fine, but the code was quite flimsy – it didn’t wrap properly for long titles, and I couldn’t get custom fonts to work. Later I rewrote the app in JavaScript, so I had access to the HTML canvas element. This is another tool that I haven’t worked with before, so a fun chance to learn something new. The API felt fairly familiar, similar to other APIs I’ve used to build HTML elements. This time I did implement some line wrapping – there’s a measureText() API for canvas, so you can see how much space text will take up before you draw it. I break the text into words, and keeping adding words to a line until measureText tells me the line is going to overflow the page. I have lots of ideas for how I could improve the line wrapping, but it’s good enough for now. I was also able to get fonts working, so I picked Georgia to match the font used for titles on AO3. Here are some examples: I had several ideas for choosing the background colour. I’m trying to help my friend browse her collection of fic, and colour would be a useful way to distinguish things – so how do I use it? I realised I could get the fandom from the EPUB file, so I decided to use that. I use the fandom name as a seed to a random number generator, then I pick a random colour. This means that all the fics in the same fandom will get the same colour – for example, all the Star Wars stories are a shade of red, while Star Trek are a bluey-green. This was a bit harder than I expected, because it turns out that JavaScript doesn’t have a built-in seeded random number generator – I ended up using some snippets from a Stack Overflow answer, where bryc has written several pseudorandom number generators in plain JavaScript. I didn’t realise until later, but I designed something similar to the placeholder book covers in the Apple Books app. I don’t use Apple Books that often so it wasn’t a deliberate choice to mimic this style, but clearly it was somewhere in my subconscious. One difference is that Apple’s app seems to be picking from a small selection of background colours, whereas my code can pick a much nicer variety of colours. Apple’s choices will have been pre-approved by a designer to look good, but I think mine is more fun. Add the cover image to the EPUB My first attempt to add a cover image used pandoc: pandoc input.epub --output output.epub --epub-cover-image cover.jpeg This approach was no good: although it added the cover image, it destroyed the formatting in the rest of the EPUB. This made it easier to find the fic, but harder to read once you’d found it. An EPUB file I downloaded from AO3, before/after it was processed by pandoc. So I tried to do it myself, and it turned out to be quite easy! I unzipped another EPUB which already had a cover image. I found the cover image in OPS/images/cover.jpg, and then I looked for references to it in content.opf. I found two elements that referred to cover images: <?xml version="1.0" encoding="UTF-8"?> <package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="PrimaryID"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf"> <meta name="cover" content="cover-image"/> … </metadata> <manifest> <item id="cover-image" href="images/cover.jpg" media-type="image/jpeg" properties="cover-image"/> … </manifest> </package> This gave me the steps for adding a cover image to an EPUB file: add the image file to the zipped bundle, then add these two elements to the content.opf. Where am I going to deploy this? I wrote the initial prototype of this in Python, because that’s the language I’m most familiar with. Python has all the libraries I need: The zipfile module can unpack and modify the EPUB/ZIP The xml.etree or lxml modules can manipulate XML The Pillow library can generate images I built a small Flask web app: you upload the EPUB to my server, my server does some processing, and sends the EPUB back to you. But for such a simple app, do I need a server? I tried rebuilding it as a static web page, doing all the processing in client-side JavaScript. That’s simpler for me to host, and it doesn’t involve a round-trip to my server. That has lots of other benefits – it’s faster, less of a privacy risk, and doesn’t require a persistent connection. I love static websites, so can they do this? Yes! I just had to find a different set of libraries: The JSZip library can unpack and modify the EPUB/ZIP, and is the only third-party code I’m using in the tool Browsers include DOMParser for manipulating XML I’ve already mentioned the HTML <canvas> element for rendering the image This took a bit longer because I’m not as familiar with JavaScript, but I got it working. As a bonus, this makes the tool very portable. Everything is bundled into a single HTML file, so if you download that file, you have the whole tool. If my friend finds this tool useful, she can save the file and keep a local copy of it – she doesn’t have to rely on my website to keep using it. What should it look like? My first design was very “engineer brain” – I just put the basic controls on the page. It was fine, but it wasn’t good. That might be okay, because the only person I need to be able to use this app is my friend – but wouldn’t it be nice if other people were able to use it? If they’re going to do that, they need to know what it is – most people aren’t going to read a 2,500 word blog post to understand a tool they’ve never heard of. (Although if you have read this far, I appreciate you!) I started designing a proper page, including some explanations and descriptions of what the tool is doing. I got something that felt pretty good, including FAQs and acknowledgements, and I added a grey area for the part where you actually upload and download your EPUBs, to draw the user’s eye and make it clear this is the important stuff. But even with that design, something was missing. I realised I was telling you I’d create covers, but not showing you what they’d look like. Aha! I sat down and made up a bunch of amusing titles for fanfic and fanfic authors, so now you see a sample of the covers before you upload your first EPUB: This makes it clearer what the app will do, and was a fun way to wrap up the project. What did I learn from this project? Don’t be scared of new file formats My first instinct was to look for a third-party library that could handle the “complexity” of EPUB files. In hindsight, I’m glad I didn’t find one – it forced me to learn more about how EPUBs work, and I realised I could write my own code using built-in libraries. EPUB files are essentially ZIP files, and I only had basic needs. I was able to write my own code. Because I didn’t rely on a library, now I know more about EPUBs, I have code that’s simpler and easier for me to understand, and I don’t have a dependency that may cause problems later. There are definitely some file formats where I need existing libraries (I’m not going to write my own JPEG parser, for example) – but I should be more open to writing my own code, and not jumping to add a dependency. Static websites can handle complex file manipulations I love static websites and I’ve used them for a lot of tasks, but mostly read-only display of information – not anything more complex or interactive. But modern JavaScript is very capable, and you can do a lot of things with it. Static pages aren’t just for static data. One of the first things I made that got popular was find untagged Tumblr posts, which was built as a static website because that’s all I knew how to build at the time. Somewhere in the intervening years, I forgot just how powerful static sites can be. I want to build more tools this way. Async JavaScript calls require careful handling The JSZip library I’m using has a lot of async functions, and this is my first time using async JavaScript. I got caught out several times, because I forgot to wait for async calls to finish properly. For example, I’m using canvas.toBlob to render the image, which is an async function. I wasn’t waiting for it to finish, and so the zip would be repackaged before the cover image was ready to add, and I got an EPUB with a missing image. Oops. I think I’ll always prefer the simplicity of synchronous code, but I’m sure I’ll get better at async JavaScript with practice. Final thoughts I know my friend will find this helpful, and that feels great. Writing software that’s designed for one person is my favourite software to write. It’s not hyper-scale, it won’t launch the next big startup, and it’s usually not breaking new technical ground – but it is useful. I can see how I’m making somebody’s life better, and isn’t that what computers are for? If other people like it, that’s a nice bonus, but I’m really thinking about that one person. Normally the one person I’m writing software for is me, so it’s extra nice when I can do it for somebody else. If you want to try this tool yourself, go to alexwlchan.net/my-tools/add-cover-to-ao3-epubs/ If you want to read the code, it’s all on GitHub. [If the formatting of this post looks odd in your feed reader, visit the original article]

7 hours ago 3 votes
Non-alcoholic apéritifs

I’ve been doing Dry January this year. One thing I missed was something for apéro hour, a beverage to mark the start of the evening. Something complex and maybe bitter, not like a drink you’d have with lunch. I found some good options. Ghia sodas are my favorite. Ghia is an NA apéritif based on grape juice but with enough bitterness (gentian) and sourness (yuzu) to be interesting. You can buy a bottle and mix it with soda yourself but I like the little cans with extra flavoring. The Ginger and the Sumac & Chili are both great. Another thing I like are low-sugar fancy soda pops. Not diet drinks, they still have a little sugar, but typically 50 calories a can. De La Calle Tepache is my favorite. Fermented pineapple is delicious and they have some fun flavors. Culture Pop is also good. A friend gave me the Zero book, a drinks cookbook from the fancy restaurant Alinea. This book is a little aspirational but the recipes are doable, it’s just a lot of labor. Very fancy high end drink mixing, really beautiful flavor ideas. The only thing I made was their gin substitute (mostly junipers extracted in glycerin) and it was too sweet for me. Need to find the right use for it, a martini definitely ain’t it. An easier homemade drink is this Nonalcoholic Dirty Lemon Tonic. It’s basically a lemonade heavily flavored with salted preserved lemons, then mixed with tonic. I love the complexity and freshness of this drink and enjoy it on its own merits. Finally, non-alcoholic beer has gotten a lot better in the last few years thanks to manufacturing innovations. I’ve been enjoying NA Black Butte Porter, Stella Artois 0.0, Heineken 0.0. They basically all taste just like their alcoholic uncles, no compromise. One thing to note about non-alcoholic substitutes is they are not cheap. They’ve become a big high end business. Expect to pay the same for an NA drink as one with alcohol even though they aren’t taxed nearly as much.

2 days ago 5 votes
It burns

The first time we had to evacuate Malibu this season was during the Franklin fire in early December. We went to bed with our bags packed, thinking they'd probably get it under control. But by 2am, the roaring blades of fire choppers shaking the house got us up. As we sped down the canyon towards Pacific Coast Highway (PCH), the fire had reached the ridge across from ours, and flames were blazing large out the car windows. It felt like we had left the evacuation a little too late, but they eventually did get Franklin under control before it reached us. Humans have a strange relationship with risk and disasters. We're so prone to wishful thinking and bad pattern matching. I remember people being shocked when the flames jumped the PCH during the Woolsey fire in 2017. IT HAD NEVER DONE THAT! So several friends of ours had to suddenly escape a nightmare scenario, driving through burning streets, in heavy smoke, with literally their lives on the line. Because the past had failed to predict the future. I feel into that same trap for a moment with the dramatic proclamations of wind and fire weather in the days leading up to January 7. Warning after warning of "extremely dangerous, life-threatening wind" coming from the City of Malibu, and that overly-bureaucratic-but-still-ominous "Particularly Dangerous Situation" designation. Because, really, how much worse could it be? Turns out, a lot. It was a little before noon on the 7th when we first saw the big plumes of smoke rise from the Palisades fire. And immediately the pattern matching ran astray. Oh, it's probably just like Franklin. It's not big yet, they'll get it out. They usually do. Well, they didn't. By the late afternoon, we had once more packed our bags, and by then it was also clear that things actually were different this time. Different worse. Different enough that even Santa Monica didn't feel like it was assured to be safe. So we headed far North, to be sure that we wouldn't have to evacuate again. Turned out to be a good move. Because by now, into the evening, few people in the connected world hadn't started to see the catastrophic images emerging from the Palisades and Eaton fires. Well over 10,000 houses would ultimately burn. Entire neighborhoods leveled. Pictures that could be mistaken for World War II. Utter and complete destruction. By the night of the 7th, the fire reached our canyon, and it tore through the chaparral and brush that'd been building since the last big fire that area saw in 1993. Out of some 150 houses in our immediate vicinity, nearly a hundred burned to the ground. Including the first house we moved to in Malibu back in 2009. But thankfully not ours. That's of course a huge relief. This was and is our Malibu Dream House. The site of that gorgeous home office I'm so fond to share views from. Our home. But a house left standing in a disaster zone is still a disaster. The flames reached all the way up to the base of our construction, incinerated much of our landscaping, and devoured the power poles around it to dysfunction. We have burnt-out buildings every which way the eye looks. The national guard is still stationed at road blocks on the access roads. Utility workers are tearing down the entire power grid to rebuild it from scratch. It's going to be a long time before this is comfortably habitable again. So we left. That in itself feels like defeat. There's an urge to stay put, and to help, in whatever helpless ways you can. But with three school-age children who've already missed over a months worth of learning from power outages, fire threats, actual fires, and now mudslide dangers, it was time to go. None of this came as a surprise, mind you. After Woolsey in 2017, Malibu life always felt like living on borrowed time to us. We knew it, even accepted it. Beautiful enough to be worth the risk, we said.  But even if it wasn't a surprise, it's still a shock. The sheer devastation, especially in the Palisades, went far beyond our normal range of comprehension. Bounded, as it always is, by past experiences. Thus, we find ourselves back in Copenhagen. A safe haven for calamities of all sorts. We lived here for three years during the pandemic, so it just made sense to use it for refuge once more. The kids' old international school accepted them right back in, and past friendships were quickly rebooted. I don't know how long it's going to be this time. And that's an odd feeling to have, just as America has been turning a corner, and just as the optimism is back in so many areas. Of the twenty years I've spent in America, this feels like the most exciting time to be part of the exceptionalism that the US of A offers. And of course we still are. I'll still be in the US all the time on both business, racing, and family trips. But it won't be exclusively so for a while, and it won't be from our Malibu Dream House. And that burns.

2 days ago 6 votes
Slow, flaky, and failing

Thou shalt not suffer a flaky test to live, because it’s annoying, counterproductive, and dangerous: one day it might fail for real, and you won’t notice. Here’s what to do.

3 days ago 7 votes
Name that Ware, January 2025

The ware for January 2025 is shown below. Thanks to brimdavis for contributing this ware! …back in the day when you would get wares that had “blue wires” in them… One thing I wonder about this ware is…where are the ROMs? Perhaps I’ll find out soon! Happy year of the snake!

3 days ago 5 votes