<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>BIT-101</title>
    <link>https://bit-101.com/blog/</link>
    <description>Recent content on BIT-101</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <managingEditor>kp@bit-101.com (Keith Peters)</managingEditor>
    <webMaster>kp@bit-101.com (Keith Peters)</webMaster>
    <lastBuildDate>Sat, 05 Sep 2026 10:33:31 -0400</lastBuildDate>
    <atom:link href="https://bit-101.com/blog/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>My Hifi Setup</title>
      <link>https://bit-101.com/blog/posts/2026-09-05/my-hifi-setup/</link>
      <pubDate>Sat, 05 Sep 2026 10:33:31 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-09-05/my-hifi-setup/</guid>
      <description>&lt;p&gt;I work from home, so I&amp;rsquo;m at my desk in my home office a lot. When I&amp;rsquo;m not in meetings, I like to listen to some music. And I like to have various sources of music available. This post shows the various pieces I use for the different music sources I listen to.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-receiver&#34;&gt;The Receiver&lt;/h2&gt;&#xA;&lt;p&gt;The core component of this whole system is my Yamaha R-S202 receiver. It&amp;rsquo;s not a crazy expensive, powerful, mega high res device, but it works well for me. It&amp;rsquo;s the bottom component here:&lt;/p&gt;&#xA;&lt;img alt=&#34;yamaha R-S202 receiver with an internet radio sitting on top of it&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-09-05/image_01.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;As a receiver, it has an AM/FM tuner and an amplifier with multiple inputs, including BlueTooth, and supports two sets of stereo speakers.&lt;/p&gt;&#xA;&lt;img alt=&#34;yamaha R-S202 receiver back showing inputs&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-09-05/image_02.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;I have some decent cheap speakers mounted to the wall above my desk, and they sound good to me. The receiver has more than enough power for listening to music at my desk.&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t really use the tuner on this device often. Most FM radio is crap, and you&amp;rsquo;re limited to what&amp;rsquo;s in your area. I have another solution for listening to live radio that allows me to pull in more and better stations from around the world.&lt;/p&gt;&#xA;&lt;h2 id=&#34;internet-radio-receiver&#34;&gt;Internet Radio Receiver&lt;/h2&gt;&#xA;&lt;p&gt;In the above image, the box on the top is an Ocean Digital WR-800 internet radio reciever. I wrote more about this device in these two posts:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2026-06-23/internet-radio/&#34;&gt;Internet Radio&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2026-06-26/internet-radio-ii/&#34;&gt;Internet Radio II&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;But to summarize, this device lets me pull in tens of thousands of live radio stations and live internet radio streams from around the world. I have 37 stations saved to my favorites and it&amp;rsquo;s great to just tune to one that has the kind of music I&amp;rsquo;m interested in and let it play while I work.&lt;/p&gt;&#xA;&lt;p&gt;This is piped into my Yamaha receiver as input one. Although it does have built-in speakers, it sounds 100x better through the receiver and external speakers.&lt;/p&gt;&#xA;&lt;h2 id=&#34;jellyfin&#34;&gt;Jellyfin&lt;/h2&gt;&#xA;&lt;p&gt;I self host a Jellyfin media server with my music, movies and saved TV shows on it. For a while I&amp;rsquo;ve been trying to figure out the best way to get that music library into my reciever so I can listen to it on the external speakers. Things I&amp;rsquo;ve tried:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Using UPnP from my Ocean Digital device. I was able to get it to connect to my music library but browsing the library on that device was slow and horrible.&lt;/li&gt;&#xA;&lt;li&gt;An old phone that streams from Jellyfin, and sends to the receiver via BlueTooth. This worked fine, but I didn&amp;rsquo;t like the form factor. A phone just sitting on my desk, moved around, you have to pick it up to see what it&amp;rsquo;s playing or start, stop, change song, etc. Yeah, first world problems, but it was just a bad UX.&lt;/li&gt;&#xA;&lt;li&gt;Did the same thing with my laptop off to the side of my desk, piping Jellyfin to the reciever via BlueTooth. This was better than the phone actually. Large screen, keyboard shortcuts, stays put. But I use my laptop for other stuff, so every morning I&amp;rsquo;d have to set it up and connect it and then undo it at the end of the work day. Yeah, I know, woe is me, I have it rough.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I also thought of setting up a Raspberry Pi with some streamer software and probably an audio hat, that could be hard wired into my receiver. But I&amp;rsquo;d still want a display. Otherwise, I&amp;rsquo;m back to using a phone to see a UI on the Pi which is streaming from my Jellyfin server. Might as well cut out the middle man and just use the phone. Looked into getting a small touch screen to attach to the PI, but that started looking complex and expensive.&lt;/p&gt;&#xA;&lt;p&gt;Then I thought, swap out the old phone for an old tablet. I had an old Kindle Fire HD10 tablet sitting around unused for years. I put the Jellyfin client on that and BlueToothed it into the receiver. Brilliant! It&amp;rsquo;s a nice big touch screen I can see and control without picking it up. It sits in one place on my desk and doesn&amp;rsquo;t move. I used this for a week or two and was pretty happy with it. My only complaint was that it was actually a little TOO big.&lt;/p&gt;&#xA;&lt;p&gt;So I went on line and bought a used Samsung Galaxy Tab A 8.0 tablet. This is from 2019, an 8-inch Android tablet. Cheap as heck. Just the right size. I applied updates to get it up to Android 11 then installed LineageOS on it which gets it to Android 15 compatibility, with Google Apps installed. Now I was able to run Symfonium, a really great, customizable music player that can act as a Jellfin client.&lt;/p&gt;&#xA;&lt;p&gt;Here it is under my big main monitor:&lt;/p&gt;&#xA;&lt;img alt=&#34;android tablet with a music player app&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-09-05/image_03.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Again, it sits right in that spot on my desk, 24/7. I don&amp;rsquo;t use it for anything else. It&amp;rsquo;s a fully dedicated music player. I can see what&amp;rsquo;s playing, control it easily. It connects to my receiver with BlueTooth, sounds great. Exactly what I was looking for all along. It&amp;rsquo;s in a plastic stand right now. But I might build some kind of heavier wooden stand for it to give it a bit more stability and weight.&lt;/p&gt;&#xA;&lt;h2 id=&#34;recorded-shows&#34;&gt;Recorded shows&lt;/h2&gt;&#xA;&lt;p&gt;A while back, I posted about a command line app I created to record live streams at scheduled times. I called it &lt;a href=&#34;https://bit-101.com/blog/posts/2026-07-26/recplay/&#34;&gt;RecPlay&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m still using that very regularly. But I improved the setup to use cron jobs to record the same shows every week. Here&amp;rsquo;s how that works:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;RecPlay is installed on a computer on my network. A small, low powered NUC on my LAN.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I have a script there called &lt;code&gt;record&lt;/code&gt; that looks like this:&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#!/bin/sh&#xA;# 1 = station, 2 = length, 3 = name&#xA;&#xA;dir=/home/keith/recording/$3&#xA;mkdir -p $dir&#xA;cd $dir&#xA;echo $(date) &amp;gt;&amp;gt; record.log&#xA;/home/keith/go/bin/recplay --station $1 --length $2 --name $3 &amp;gt;&amp;gt; record.log&#xA;&#xA;echo &amp;#34;recording complete!&amp;#34; &amp;gt;&amp;gt; record.log&#xA;&#xA;echo &amp;#34;renaming to m4a...&amp;#34; &amp;gt;&amp;gt; record.log&#xA;for file in `ls ./*.mp4` ; do&#xA;  echo $file &amp;gt;&amp;gt; record.log&#xA;  mv $file ${file%.mp4}.m4a&#xA;done&#xA;&#xA;echo &amp;#34;syncing...&amp;#34; &amp;gt;&amp;gt; record.log&#xA;rsync -avz --ignore-existing --remove-source-files -e &amp;#34;ssh -i /home/keith/.ssh/&amp;lt;sshkey&amp;gt;&amp;#34; ./*.m4a &amp;lt;path to media on nas&amp;gt; &amp;gt;&amp;gt; record.log&#xA;&#xA;echo &amp;#34;====================\n&amp;#34; &amp;gt;&amp;gt; record.log&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Some obfuscation there, but basically it lets you pass in a station, length and name of show.&lt;/p&gt;&#xA;&lt;p&gt;It changes into a dedicated directory for that show name and calls &lt;code&gt;recplay&lt;/code&gt; to immediately record that stream for the given duration.&lt;/p&gt;&#xA;&lt;p&gt;When the recording is done it changes the extension to &lt;code&gt;m4a&lt;/code&gt; (&lt;code&gt;mp4&lt;/code&gt; is good for capturing a wide variety of streams, but Jellyfin sees that as a video, and &lt;code&gt;m4a&lt;/code&gt; as audio.)&lt;/p&gt;&#xA;&lt;p&gt;Then it syncs it to my NAS where Jellyfin is running.&lt;/p&gt;&#xA;&lt;p&gt;I log each step in case things go wrong.&lt;/p&gt;&#xA;&lt;p&gt;Notice that I did not use the scheduling feature of &lt;code&gt;recplay&lt;/code&gt;. It starts recording immediately. That&amp;rsquo;s because I schedule all these via crontab. Here&amp;rsquo;s what what that looks like:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;0 9 * * 2  /home/keith/recording/record wvew 01:30:00 Rustle_in_the_Bushes&#xA;0 13 * * 3 /home/keith/recording/record wvew 02:00:00 Post_Punk_Romantics&#xA;0 6 * * 3  /home/keith/recording/record wmbr 01:00:00 It_Came_From_Planet_Claire&#xA;0 7 * * 3  /home/keith/recording/record wmbr 01:00:00 Morning_Wood&#xA;0 17 * * 4 /home/keith/recording/record wers 01:00:00 Throwback_Thursday&#xA;0 10 * * 6 /home/keith/recording/record wvew 02:00:00 Psychedelic_Train&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So at the given time and day of week, it records a given show for a given duration, by calling the above script.&lt;/p&gt;&#xA;&lt;p&gt;Each recording automatically becomes part of my Jellyfin library, so I can play them from my tablet if I&amp;rsquo;m at my desk, or from my phone if I&amp;rsquo;m out and about.&lt;/p&gt;&#xA;&lt;p&gt;Pretty happy with this whole set up.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Yuki</title>
      <link>https://bit-101.com/blog/posts/2026-07-28/yuki/</link>
      <pubDate>Tue, 28 Jul 2026 17:59:00 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-07-28/yuki/</guid>
      <description>&lt;p&gt;I lost one of my best friends ever today.&lt;/p&gt;&#xA;&lt;img alt=&#34;siberian husky in the snow&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-28/yuki.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I saved this poem years ago, knowing that it would bring me comfort on this day.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&#34;The House-Dog’s Grave&#34;&#xA;&lt;p&gt;I’ve changed my ways a little: I cannot now&lt;br/&gt;&#xA;Run with you in the evenings along the shore,&lt;br/&gt;&#xA;Except in a kind of dream: and you, if you dream a moment,&lt;/br&gt;&#xA;You see me there.&lt;/br&gt;&lt;/p&gt;&#xA;&lt;p&gt;So leave awhile the paw-marks on the front door&lt;/br&gt;&#xA;Where I used to scratch to go out or in,&lt;/br&gt;&#xA;And you’d soon open; leave on the kitchen floor&lt;/br&gt;&#xA;The marks of my drinking-pan.&lt;/br&gt;&lt;/p&gt;&#xA;&lt;p&gt;I cannot lie by your fire as I used to do&lt;/br&gt;&#xA;On the warm stone,&lt;/br&gt;&#xA;Nor at the foot of your bed: no, all the nights through&lt;/br&gt;&#xA;I lie alone.&lt;/br&gt;&lt;/p&gt;&#xA;&lt;p&gt;But your kind thought has laid me less than six feet&lt;/br&gt;&#xA;Outside your window where firelight so often plays,&lt;/br&gt;&#xA;And where you sit to read — and I fear often grieving for me —&lt;/br&gt;&#xA;Every night your lamplight lies on my place.&lt;/br&gt;&lt;/p&gt;&#xA;&lt;p&gt;You, man and woman, live so long it is hard&lt;/br&gt;&#xA;To think of you ever dying.&lt;/br&gt;&#xA;A little dog would get tired living so long.&lt;/br&gt;&#xA;I hope that when you are lying&lt;/br&gt;&lt;/p&gt;&#xA;&lt;p&gt;Under the ground like me your lives will appear&lt;/br&gt;&#xA;As good and joyful as mine.&lt;/br&gt;&#xA;No, dears, that’s too much hope: you are not so well cared for&lt;/br&gt;&#xA;As I have been,&lt;/br&gt;&lt;/p&gt;&#xA;&lt;p&gt;And never have known the passionate undivided&lt;/br&gt;&#xA;Fidelities that I knew.&lt;/br&gt;&#xA;Your minds are perhaps too active, too many-sided. …&lt;/br&gt;&#xA;But to me you were true.&lt;/br&gt;&lt;/p&gt;&#xA;&lt;p&gt;You were never masters, but friends. I was your friend.&lt;/br&gt;&#xA;I loved you well, and was loved. Deep love endures&lt;/br&gt;&#xA;To the end and far past the end. If this is my end,&lt;/br&gt;&#xA;I am not lonely. I am not afraid. I am still yours.&lt;/br&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;— Robinson Jeffers&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Recplay</title>
      <link>https://bit-101.com/blog/posts/2026-07-26/recplay/</link>
      <pubDate>Sun, 26 Jul 2026 19:42:54 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-07-26/recplay/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been getting heavy into internet radio lately, as mentioned in &lt;a href=&#34;http://localhost:1313/blog/posts/2026-06-23/internet-radio/&#34;&gt;this post&lt;/a&gt; and &lt;a href=&#34;http://localhost:1313/blog/posts/2026-06-26/internet-radio-ii/&#34;&gt;that post&lt;/a&gt;. A lot of what I listen to day to day is just music of a specific genre. You just let it play and it&amp;rsquo;s the same general stuff all day. But there are also some local stations and college stations, as well as NPR affiliated stations that I listen to that have schedules of different shows at different times of the day and week. Some of these are a DJ playing a specific type of music for an hour or two, or maybe a live show, sometimes interviews, news, talk shows, etc. Since these come on at specific times of the day, you either have to listen to them when they come on, or maybe they have an archive you can listen to some time later. Or&amp;hellip; you can record them.&lt;/p&gt;&#xA;&lt;p&gt;I spent a lot of time thinking about recording hardware. I considered getting a new or vintage cassette deck. I looked into hifi hardware digital recorders. There are surprisingly few of these, and they are outrageously expensive. I&amp;rsquo;m really surprised there aren&amp;rsquo;t more devices like this. It would just take an audio capture board and an SBC (single board computer) and some software and buttons. I started looking into making one myself. A raspberry Pi with an audio hat. But goddamn, rPis are so expensive now. rPi 5 with 4GB RAM is over well $100. 16GB is $350. That&amp;rsquo;s before you get a case and a power supply and a cooling fan and the audio hat. I looked into other boards that are less expensive, but in the end I changed gears a bit.&lt;/p&gt;&#xA;&lt;p&gt;I decided to do it all in software. ffmpeg can record an live audio stream, like so:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ffmpeg -i https://some-audio-stream -c copy recording.mp4&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So simple. But there&amp;rsquo;s so much more you can do with it. I wanted to be able to schedule future shows - have it start at a specific time. And record for a given amount of time - 30 minutes, an hour, two hours, whatever. And I didn&amp;rsquo;t want to have to type in streams, just the name of the station.&lt;/p&gt;&#xA;&lt;h2 id=&#34;recplay&#34;&gt;recplay!&lt;/h2&gt;&#xA;&lt;p&gt;So I built a little command line app in Go. It calls out to ffmpeg to do the actual capture and save, but it takes care of the scheduling, catalog of stations, text ui, etc. It does almost everything I need it to at this point, but I still have ideas for more features.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s called &lt;code&gt;recplay&lt;/code&gt; because it records and plays streams, but also because on the old cassette recorders I had as a kid, you had to press the record button AND the play button at the same time in order to record. See the image at the top of the page.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the help to give you an idea of how it works:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-26/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;In general, you specify a stream/station, and a date, time and duration to record, and an output file name. But you can skip most of those.&lt;/p&gt;&#xA;&lt;p&gt;Or, you can choose a station and play that station without recording.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the list of stations, so you don&amp;rsquo;t have to remember long stream URLs:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-26/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;If you just specify a station, it will start recording immediately for a default 30 minutes.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-26/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Also notice that if you don&amp;rsquo;t specify an output name, it makes one up.&lt;/p&gt;&#xA;&lt;p&gt;If you want a longer or shorter recording, change the length.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-26/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;If you want to record at a specific time, you can do that. Without a date, it assumes today.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-26/image_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Or specify another date.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-26/image_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Of course, on *nix systems, you can throw a &lt;code&gt;&amp;amp;&lt;/code&gt; on the end of the command to background it. It&amp;rsquo;ll still record and exit when it&amp;rsquo;s done.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been using this all weekend, scheduling and recording all kinds of streams, and listening to them later. One of the biggest pain points was making mistakes in choosing the right date and time. Especially remembering that it has to be in 24 hour time. I&amp;rsquo;d type 08:00:00 thinking it was 8 pm, not am. So I fixed that up a bit&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-26/image_07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Still, sometimes I mess up and wind up with a date/time that&amp;rsquo;s in the past. In that case, the app will start recording immediately, confusing me. So I patched that one&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-26/image_08.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;the-station-list&#34;&gt;The Station List&lt;/h2&gt;&#xA;&lt;p&gt;Initially of course, I hardcoded the station list, but eventually I knew it&amp;rsquo;d have to be externally editable. Now, when you start the app for the first time it looks for a config file in the system user config location. If it&amp;rsquo;s not there, it creates it with a few sample stations. This is just a json file that looks like this:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{&#xA;  &amp;#34;WERS&amp;#34;: &amp;#34;https://15313.live.streamtheworld.com:443/WERSFM_SC&amp;#34;,&#xA;  &amp;#34;WMBR&amp;#34;: &amp;#34;https://wmbr.org:8002/hi&amp;#34;,&#xA;  &amp;#34;WVEW&amp;#34;: &amp;#34;https://audio-edge-n9hx8.yul.o.radiomast.io/20b6f77f-2177-46a2-b461-437a84273d5f&amp;#34;,&#xA;  &amp;#34;WBUR&amp;#34;: &amp;#34;https://fm909.wbur.org/wbur_www&amp;#34;,&#xA;  &amp;#34;WNYC&amp;#34;: &amp;#34;https://fm939.wnyc.org/wnycfm&amp;#34;,&#xA;  &amp;#34;BBC4&amp;#34;: &amp;#34;http://as-hls-ww-live.akamaized.net/pool_55057080/live/ww/bbc_radio_fourfm/bbc_radio_fourfm.isml/bbc_radio_fourfm-audio%3d96000.norewind.m3u8&amp;#34;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once it&amp;rsquo;s there, you can add whatever you want to it, delete, change the URLs if you find a better one, etc.&lt;/p&gt;&#xA;&lt;h2 id=&#34;hardware&#34;&gt;Hardware&lt;/h2&gt;&#xA;&lt;p&gt;Initially I was just running this on my laptop, but for scheduling recording, that meant I had to keep my laptop from sleeping. Eventually I got a bit of hardware to run it on. A tiny NUC - a mini computer. The one I got cost only $169 US. It&amp;rsquo;s about 6 inches square and a couple inches high. It&amp;rsquo;s very low powered. Came with Windows 11 which ran like a dog. But I wiped that and put Ubuntu Server on it and it&amp;rsquo;s great. I spent a bunch of time setting up that enviornment and getting it so I can securely ssh into it. I put tmux on it, so I can just open a tmux pane, schedule a few recordings, then detach from that session and it stays running on the server. The recordings then get SCP&amp;rsquo;ed over to my NAS where they appear in my Jellyfin server. Quite happy with this setup.&lt;/p&gt;&#xA;&lt;h2 id=&#34;todo&#34;&gt;TODO&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m pretty happy where this has wound up, but I have a few other things in mind.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Some shows might start a few seconds early or go a couple minutes late. I&amp;rsquo;ve been manually starting them to record at times such as 8:59 and setting the length to be a couple minutes longer. I&amp;rsquo;m thinking of making a setting that does that automatically.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I started looking into some overseas stations like the BBC and realized that the UK is not in the same time zone as the eastern US. Who knew!? Doing time-based math is not fun, so I may have to put a time zone in the recording parameter.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;dependencies&#34;&gt;Dependencies&lt;/h2&gt;&#xA;&lt;p&gt;As mentioned in the start, this is all just a fancy wrapper around ffmpeg, so you need to have that installed. The play functionality uses ffplay, which should come with your ffmpeg install I think.&lt;/p&gt;&#xA;&lt;h2 id=&#34;check-it-out&#34;&gt;Check it out!&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://codeberg.org/bit101/recplay&#34;&gt;https://codeberg.org/bit101/recplay&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>More on Drawing Crisp Lines</title>
      <link>https://bit-101.com/blog/posts/2026-07-08/more-on-drawing-crisp-lines/</link>
      <pubDate>Wed, 08 Jul 2026 13:02:39 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-07-08/more-on-drawing-crisp-lines/</guid>
      <description>&lt;p&gt;Following up on &lt;a href=&#34;https://bit-101.com/blog/posts/2026-07-07/drawing-crisp-lines/&#34;&gt;yesterday&amp;rsquo;s post&lt;/a&gt; about drawing crisp lines, I realized it would be nice to add some details on why putting lines on a 0.5 pixel makes them crisp and not blurry.&lt;/p&gt;&#xA;&lt;p&gt;In many common drawing apis, when you draw lines, the pixel values you give mark the center of the line. If you are drawing a one-pixel thick line, it will draw it half a pixel on either side of the measurment you supplied. Because it crosses pixel boundaries, automatic antialiasing kicks in and the line is blurred to make it look like it is in between two pixels.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s an simulated image that might help:&lt;/p&gt;&#xA;&lt;img alt=&#34;three lines on a grid&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-08/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here, each of the squares in the grid represents one pixel. Each of those pixels is measured not by its center, but by its top left corner. So if you set pixel 0, 0 to black, that whole top left pixel would turn black.&lt;/p&gt;&#xA;&lt;p&gt;But when you draw a vertical line at a certain whole number pixel value, it puts half that line on the previous pixel column, and half on the given pixel column. So you get a blur.&lt;/p&gt;&#xA;&lt;p&gt;In the second case, this is a one-pixel wide rectangle drawn at whole pixel boundaries. As you can see it only affects whole pixels. And it&amp;rsquo;s sharp and crisp.&lt;/p&gt;&#xA;&lt;p&gt;The third image respresents a line drawn on a 0.5 pixel value. It draws the line half a pixel to the right and half a pixel to the left, which exactly fills that column of pixels, so it is crisp, just like the rectangle. No antialiasing is applied.&lt;/p&gt;&#xA;&lt;p&gt;In the image below, I&amp;rsquo;ve drawn a series of one-pixel rectangles on the top half. Below that is a series of one-pixel lines, each with a bit of a horizontal offset. The one of the far left has no offset and is on an exact pixel, so if very blurry and looks thick. Moving left to right, each pixel has a bit more of an offset until the last one has a 0.5 pixel offset. This puts it within a full pixel and there&amp;rsquo;s no antialiasing.&lt;/p&gt;&#xA;&lt;img alt=&#34;three lines on a grid&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-08/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;This also applies to drawing rectangles. But in the opposite way. Rectangles should be on whole pixel values if you wan them to appear crisp.&lt;/p&gt;&#xA;&lt;p&gt;Below are two squares, and two one-pixel wide rectangles. In both cases the first example is on a 0.5 pixel boundary, and the next one is on an exact pixel. The first ones look blurry and the next ones are crisp.&lt;/p&gt;&#xA;&lt;img alt=&#34;three lines on a grid&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-08/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Finally, since we&amp;rsquo;re talking about pixels, let&amp;rsquo;s look at some honest-to-god PIXELS!&lt;/p&gt;&#xA;&lt;p&gt;Using my USB microscope, here&amp;rsquo;s the left part of that second picture showing the whole pixel rectangle and whole pixel line. The top half with the rectangle only affects the single pixel. Below that it affects some to the left of that column, and overall it&amp;rsquo;s less dark.&lt;/p&gt;&#xA;&lt;img alt=&#34;three lines on a grid&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-08/image_04.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;And we can zoom in even more and really see it clearly.&lt;/p&gt;&#xA;&lt;img alt=&#34;three lines on a grid&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-08/image_05.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here, in the rectangle portion, the entire set of red, green and blue elements is blacked out completely. And the ones on either side of it are fully on.&lt;/p&gt;&#xA;&lt;p&gt;But on the blurred version, a wider area is just dimmed down. That&amp;rsquo;s the antialiasing at work.&lt;/p&gt;&#xA;&lt;p&gt;Further to the right in that same image, the line is at more of an offset and the antialiasing is less prominent, but it&amp;rsquo;s not quite the same as the rectangle.&lt;/p&gt;&#xA;&lt;img alt=&#34;three lines on a grid&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-08/image_06.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;And on the far right, where the line is on a 0.5 pixel value, there is no difference between the line and the rectangle.&lt;/p&gt;&#xA;&lt;img alt=&#34;three lines on a grid&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-08/image_07.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;If there&amp;rsquo;s any real takeaway from this post, it&amp;rsquo;s: Get a USB microscope! They are awesome!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Drawing Crisp Lines</title>
      <link>https://bit-101.com/blog/posts/2026-07-07/drawing-crisp-lines/</link>
      <pubDate>Tue, 07 Jul 2026 16:54:28 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-07-07/drawing-crisp-lines/</guid>
      <description>&lt;p&gt;Today I was working on some code where I was drawing a bunch of thin lines for various types of grids. After I had the basic code working, I went back and worked on cleaning things up. One thing I&amp;rsquo;d noticed is that some of the lines were&amp;hellip; fuzzy. They were absolutely straight vertical and horizontal lines, but some were clean and crisp, others looked kind of blurry. And they all seemed to have slightly different widths.&lt;/p&gt;&#xA;&lt;img alt=&#34;blurry lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-07/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Fortunately, I knew just what was going on. Tips I&amp;rsquo;d picked up on in the early 2000s working with Flash. I knew just what to do to make them clean and crisp and uniform. This worked in Flash, it works in JavaScript, it works in my library which is based on CairoGraphics. Native Processing doesn&amp;rsquo;t have this issue, but p5.js which is in the browser, does.&lt;/p&gt;&#xA;&lt;p&gt;What&amp;rsquo;s happening is that even though the lines are completely vertical, they wind up at various locations on the y-axis. I purposely set up the code to make sure this was the case. I&amp;rsquo;m a bit rusty on my JavaScript, but put together this snippet:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; width &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;773&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; height &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; canvas &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;document&lt;/span&gt;.getElementById(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;canvas&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; context &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; canvas.getContext(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;2d&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;canvas.width &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; width;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;canvas.height &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; height;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.fillStyle &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;white&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.fillRect(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, width, height);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.lineWidth &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; count &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;57&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.beginPath()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.moveTo(i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; count &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; width, &lt;span style=&#34;color:#f60&#34;&gt;25&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.lineTo(i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; count &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; width, &lt;span style=&#34;color:#f60&#34;&gt;375&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because I used random odd numbers for the width and count, they won&amp;rsquo;t divide evenly and most lines will be at some fraction of a pixel on the x-axis.&lt;/p&gt;&#xA;&lt;p&gt;So part of what you want to do is round those x values off. We&amp;rsquo;ll start there. Just showing the loop this time&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.beginPath()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.moveTo(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.round(i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; count &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; width), &lt;span style=&#34;color:#f60&#34;&gt;25&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.lineTo(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.round(i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; count &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; width), &lt;span style=&#34;color:#f60&#34;&gt;375&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That gets us here:&lt;/p&gt;&#xA;&lt;img alt=&#34;blurry lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-07/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Not great. They&amp;rsquo;re all uniform now, which is nice&amp;hellip; but they&amp;rsquo;re all uniformly blurry. Well, I said that rounding was part of the solution. The next part is to move the line so it&amp;rsquo;s exactly on a half pixel. We can do that by just adding 0.5 to each value&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.beginPath()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.moveTo(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.round(i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; count &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; width) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;25&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.lineTo(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.round(i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; count &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; width) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;375&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now, voila!&lt;/p&gt;&#xA;&lt;img alt=&#34;clean lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-07/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;These look very clean and crisp.&lt;/p&gt;&#xA;&lt;p&gt;But we can do better. With the above code, lines might move a bit further than you really want them to in some cases.&lt;/p&gt;&#xA;&lt;p&gt;For example, say your x value was 103.57.&lt;/p&gt;&#xA;&lt;p&gt;We round that to get 104.0 and then add 0.5 for 104.5.&lt;/p&gt;&#xA;&lt;p&gt;We&amp;rsquo;ve moved it almost a full pixel to the right. A better spot for it would be 103.5, just 0.07 pixels to the left.&lt;/p&gt;&#xA;&lt;p&gt;To handle that, we can make a function like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; roundHalfPixel(value) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.round(value &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;) &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We add 0.5, round and subtract 0.5.&lt;/p&gt;&#xA;&lt;p&gt;So our 103.57 becomes 104.07. Rounded to 104.0 then subtract 0.5 to get 103.5. Just what we want.&lt;/p&gt;&#xA;&lt;p&gt;And if we&amp;rsquo;re at something like 103.21, we add 0.5 to get 103.71, round to 104.0 and subtract 0.5 to get 103.5, which again is the closest half pixel.&lt;/p&gt;&#xA;&lt;p&gt;Fixing up the code to use this function&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; count; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.beginPath()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; roundHalfPixel(i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; count &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; width);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.moveTo(x, &lt;span style=&#34;color:#f60&#34;&gt;25&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.lineTo(x, &lt;span style=&#34;color:#f60&#34;&gt;375&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  context.stroke();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that gives you the final image:&lt;/p&gt;&#xA;&lt;img alt=&#34;clean lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-07/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Honestly you probably won&amp;rsquo;t see much of a difference here in most cases, but it&amp;rsquo;s more correct and just might make something look better someday.&lt;/p&gt;&#xA;&lt;p&gt;This will work on lines of any width. But sometimes the result is more striking than others. I&amp;rsquo;ve been using 0.5 pixel lines and the effect is subtle, but there. Here&amp;rsquo;s a comparison of the original code and the final code, with 1-pixel lines.&lt;/p&gt;&#xA;&lt;img alt=&#34;blurry and clean lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-07-07/image_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I only showed vertical lines, but it applies to horizontal lines too. You really only need this for straight vertical and horizontal lines mostly. But give it a shot on angled lines and see if it makes a difference. It&amp;rsquo;s also good on any rectangular fills (but use whole values in this case, as covered in &lt;a href=&#34;https://bit-101.com/blog/posts/2026-07-08/more-on-drawing-crisp-lines/&#34;&gt;the next post&lt;/a&gt;), gives them crisp edges. But don&amp;rsquo;t take my word for it. See what it does on your system.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s all. I just thought of this trick today and realized that I learned this more than two decades ago. And I&amp;rsquo;m not sure if I&amp;rsquo;ve ever heard anyone mention this trick or read anything about it any time recently. Wondered if it&amp;rsquo;s a lost bit of knowledge among newer creative coders. Maybe not, but figured it wouldn&amp;rsquo;t hurt to toss it out there in the wild again.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Part of the Problem</title>
      <link>https://bit-101.com/blog/posts/2026-07-03/part-of-the-problem/</link>
      <pubDate>Fri, 03 Jul 2026 13:07:00 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-07-03/part-of-the-problem/</guid>
      <description>&lt;p&gt;Anyone who follows me on line knows that I&amp;rsquo;m not the biggest fan of AI and where it is taking the world. I won&amp;rsquo;t make a huge rant about it, but I didn&amp;rsquo;t get in to programming to just describe what I want to code, have something code it, then point out the mistakes it made. Beyond that, there&amp;rsquo;s all the impacts on the world, the environment, the economy, workers, artists, other coders, and the kids who will grow up in this new world. That&amp;rsquo;s it. That&amp;rsquo;s my rant.&lt;/p&gt;&#xA;&lt;p&gt;But recently, I have been wondering about my part in all this. I&amp;rsquo;ve been coding as a hobby for around 40 years and professionally for almost 30. I&amp;rsquo;ve always been excited about the next big thing. The new OS, new hardware, new devices. I remember a world WAY before everyone had a computer at home, much less an even more powerful computer in their pocket, or on their wrist. I remember a world before any normal person had access to the internet or even knew what it was. And I rejoiced in every new capability. I can have my own computer? Amazing! Doom? Real time 3D? Holy crap! I can render fractals on my own? Wow! I can download an album or any number of songs without having to buy physical media? Now I can stream that music? I can have a computer that I can carry around with me? Now I can have one that fits in my pocket? And I can use these things to talk to anyone anywhere in the world? WTF? I can create a page on this internet that anyone can see? I can write code and put it on my website, and have it to real time interactive physics simulations that anyone in the world can see as soon as I put it there? I can stream entire movies to any device? I can make my own movies and let other people stream them? Goddamn!&lt;/p&gt;&#xA;&lt;p&gt;Every step, every new big thing I met with enthusiasm and excitement. I never really questioned where it was all leading. I guess I thought about it sometimes, but with rose colored glasses. It was just going to keep getting better and faster and higher resolution. I actually remember thinking that now that anyone in the world could talk to anyone else in the world instantaneously any time day or night, that war would inevitably die off. So naive.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, we finally got to something that I do not meet with enthusiasm and excitement. I meet it with concern and anxiety.&lt;/p&gt;&#xA;&lt;p&gt;But I think&amp;hellip; I think that this was always where it was leading. Faster, better, easier, more access to data, faster processing of that data. Less friction, more automation. There&amp;rsquo;s an app for that, and that, and yeah, that too. There&amp;rsquo;s no earning anything, no waiting, no saving up, not even any &lt;em&gt;thinking&lt;/em&gt; about how to do anything. Just pay your monthly subscription and you have it all literally without lifting a finger. How is AI not the end game in this progression?&lt;/p&gt;&#xA;&lt;p&gt;And I think I am complicit in this. I bought into it at every step along the way. I wrote about it, talked about it, shared the excitement about each little turn in the path. And now we are here and I don&amp;rsquo;t like where we&amp;rsquo;ve come. That doesn&amp;rsquo;t feel great.&lt;/p&gt;&#xA;&lt;p&gt;In some ways, personally, I have an out. I&amp;rsquo;m in my 60s now. I can retire in the next few years. And leave this whole mess behind. But if I were still in my 30s or 40s now, I&amp;rsquo;d be having a serious crisis.&lt;/p&gt;&#xA;&lt;p&gt;So for anyone younger, who is going to have to live with this for the next few decades, I&amp;rsquo;m sorry. We were so preoccupied with whether or not we could&amp;hellip; etc.&lt;/p&gt;&#xA;&lt;p&gt;Please do better than those who came before you.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Internet Radio II</title>
      <link>https://bit-101.com/blog/posts/2026-06-26/internet-radio-ii/</link>
      <pubDate>Fri, 26 Jun 2026 17:40:05 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-06-26/internet-radio-ii/</guid>
      <description>&lt;p&gt;The other day I posted about my &lt;a href=&#34;https://bit-101.com/blog/posts/2026-06-23/internet-radio/&#34;&gt;internet radio explorations&lt;/a&gt;. A few people weighed in with their experiences and questions. You can see those in the comments of that post. Mostly it was me talking about adding streams, so I figured I&amp;rsquo;d more permanently document that in another post. This is mostly about finding, adding, and managing new stations.&lt;/p&gt;&#xA;&lt;h2 id=&#34;skytune&#34;&gt;Skytune&lt;/h2&gt;&#xA;&lt;p&gt;As mentioned, my receiver, and many others use the &lt;a href=&#34;https://skytune.net&#34;&gt;Skytune&lt;/a&gt; service. This is a huge database of streams, with metadata, images, locations, genres, etc. Probably at least 30,000. And they maintain the list pretty well. I&amp;rsquo;ve used other databases of streams that were pretty hit and miss. But in my experience it&amp;rsquo;s pretty rare for a Skytune stream to not work. I imagine they license the service to all these receiver makers and have the resources to keep their lists up to date. And if they slacked off, the manufacturers would look for other providers.&lt;/p&gt;&#xA;&lt;p&gt;While you can search for and manage Skytune stations on the device, it&amp;rsquo;s not a great experience. Better is to open up &lt;a href=&#34;https://skytune.net&#34;&gt;https://skytune.net&lt;/a&gt; in your browser, on the same network your receiver is on. You&amp;rsquo;ll get to a search form like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You can filter by genre or location, and enter a search term. Here&amp;rsquo;s what I got for &amp;ldquo;SomaFM&amp;rdquo;:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;How you are supposed to use this page confused me at first, because clicking on the thumbnails does nothing. Finally I figured out you have to click on the little heart icon on one of the stations. This brings up a modal like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;There, you enter your receiver&amp;rsquo;s IP address (which is displayed on its screen) and whether you want to save the station or play it now. When you do that, it goes to the page where you can manage all your saved stations. This is a page served directly from the device itself. You can get there any time by entering your device&amp;rsquo;s IP address in the browser.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;As you can see, I saved SomaFM&amp;rsquo;s &amp;ldquo;Drone Zone&amp;rdquo; station. But I already had another version of it saved. This happens now and then. There can be different versions. Sometimes different official versions of a station with different bandwidths, sometimes, I don&amp;rsquo;t know, unoffical versions too? You can try different ones and see if any are better than others.&lt;/p&gt;&#xA;&lt;p&gt;Here you can also play, edit, delete or re-arrange your stations. The UI is pretty minimal, but is workable and stable from what I&amp;rsquo;ve seen.&lt;/p&gt;&#xA;&lt;p&gt;You can also see what&amp;rsquo;s currently playing, change the volume or mute. Oddly, I don&amp;rsquo;t see a way to stop anything from playing.&lt;/p&gt;&#xA;&lt;h2 id=&#34;non-skytune-stations&#34;&gt;Non Skytune Stations&lt;/h2&gt;&#xA;&lt;p&gt;Although Skytune has thousands of stations, eventually you&amp;rsquo;ll find a station you want to save that is not in their database.&lt;/p&gt;&#xA;&lt;p&gt;So, you can add a new station. You&amp;rsquo;ll need a working audio stream URL and some info about the station - name, location, genre. Plug all that in in the bottom of that same management page, and the station will be added to your favorites.&lt;/p&gt;&#xA;&lt;p&gt;There are some differences between Skytune and non-Skytune stations. Skytune does add some value for the stations in its database. It gets a thumbnail image, and generally displays more info on the screen than a manually added stream. For example, here&amp;rsquo;s a manually added station I put on today.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_05.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Just a default image. Some stream stats when it starts up, but then it&amp;rsquo;s just blank.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_06.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;But other manual streams will show more info, such as the currently playing song, like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_07.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;It just depends on what the stream sends over for metadata. I haven&amp;rsquo;t run across any streams that display anything but the default image though.&lt;/p&gt;&#xA;&lt;p&gt;Compare that to an official Skytune station for SomaFM Groove Salad, with the official thumbnail, tag line of &amp;ldquo;Ambient Chill&amp;rdquo;, and what&amp;rsquo;s playing.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_09.jpg&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;where-to-find-stream-urls&#34;&gt;Where to find stream URLs&lt;/h2&gt;&#xA;&lt;p&gt;So where do you find the URLs to input? A few answers. Some sites will just give you the URL. Like SomaFM&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;If your station doesn&amp;rsquo;t have a published stream URL, there are various sites that document these, with thousands of streams listed. Here are a few:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.radio-browser.info/&#34;&gt;radio-browser.info&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://fmstream.org/&#34;&gt;fmstream.org&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://streamurl.link/&#34;&gt;streamurl.link&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;There are lots of sites that let you play streams from the site, but these ones offer the actual URLs to the streams to use in other devices.&lt;/p&gt;&#xA;&lt;p&gt;And if you still can&amp;rsquo;t find a stream URL, there&amp;rsquo;s the hacker method.&lt;/p&gt;&#xA;&lt;p&gt;Go to the page that lets you play a stream. Open the developer tools in your browser and go to the network tab. Filter for media files and play the stream. You might need to refresh if you didn&amp;rsquo;t get the timing right. Eventually you&amp;rsquo;ll get at least one listing there. Sometimes more. There are many possible configurations for streaming audio, so you might have to find the right listing by trial and error. But one of them should work.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_09.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;submitting-a-station-to-skytune&#34;&gt;Submitting a station to Skytune&lt;/h2&gt;&#xA;&lt;p&gt;Say you find an official stream URL for a station that&amp;rsquo;s not in Skytune. You can submit it by going to &amp;ldquo;Suggest Station&amp;rdquo; on the main site. They ask for a bunch of info, and I have no idea what their turnaround is, but it&amp;rsquo;s worth a shot.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-26/image_10.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;other&#34;&gt;Other&lt;/h2&gt;&#xA;&lt;p&gt;I should add that it&amp;rsquo;s not just music. There are news stations, talk stations, even podcasts stored on Skytune. I have several news stations and a bunch of BBC stations.&lt;/p&gt;&#xA;&lt;p&gt;Oh, and Skytune lets you save your stations to disk and restore them from a saved file. Sadly, they are saved in some kind of binary text file, rather than something like JSON which might be usable elsewhere. Open source, this is not. :( But it&amp;rsquo;s a good idea to make a backup now and then.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Internet Radio</title>
      <link>https://bit-101.com/blog/posts/2026-06-23/internet-radio/</link>
      <pubDate>Tue, 23 Jun 2026 19:22:23 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-06-23/internet-radio/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;I started buying music in the 70s. Vinyl. Not because it was cool, but because that&amp;rsquo;s how you bought music. It was either that or 8-track tapes. I never had an 8-track player. I had a record player. We didn&amp;rsquo;t call them &amp;ldquo;turntables&amp;rdquo;. We called them record players. Again, it wasn&amp;rsquo;t trendy. That&amp;rsquo;s just how it was. I wound up with a respectable vinyl library.&lt;/p&gt;&#xA;&lt;p&gt;Cassettes were a thing too, but nobody &lt;em&gt;bought music&lt;/em&gt; on cassette. You bought vinyl and recorded it to cassettes if you wanted to play it in your car. Walkmans (Walkmen???) weren&amp;rsquo;t a thing just yet, but they were around the corner. When I got a car, I invested in a decent car stereo with a cassette player and recorded many of my favorite albums and those cassettes lived in my car.&lt;/p&gt;&#xA;&lt;p&gt;I left home early and at some point I wound up selling all my vinyl to eat. Sad. I still had my cassettes though. And I gave up my car and bought a Walkman. Not a real Sony Walkman, but a series of Walkman clones. That&amp;rsquo;s when I started buying new cassettes. I built up a respectable cassette library.&lt;/p&gt;&#xA;&lt;p&gt;Then came CDs. I didn&amp;rsquo;t buy into that format though. I&amp;rsquo;d borrow my friends&amp;rsquo; CDs and record them to cassettes.&lt;/p&gt;&#xA;&lt;p&gt;Then came MP3s. I bought the first commercially available MP3 Player, the Diamond Rio PMP300.&lt;/p&gt;&#xA;&lt;img alt=&#34;mp3 player&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-23/rio.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Over the next many years I got rid of my cassettes (I don&amp;rsquo;t actually remember what I did with them) and built up a decent library of MP3s. And in the last bunch of years I&amp;rsquo;ve been upgrading those MP3s to lossless FLACs. I have a Jellyfin server at home, with TailScale, so I can access that music anywhere. And I have a few high res portable digital audio players.&lt;/p&gt;&#xA;&lt;p&gt;So I&amp;rsquo;m in pretty good shape with my own personal music library.&lt;/p&gt;&#xA;&lt;h2 id=&#34;streaming&#34;&gt;Streaming&lt;/h2&gt;&#xA;&lt;p&gt;But sometimes I want to just turn something on and let it choose the music for me. Music that I may not have in my collection. Music that I may never heard of.&lt;/p&gt;&#xA;&lt;p&gt;When I was growing up, this was called FM radio. It&amp;rsquo;s still an option, but it&amp;rsquo;s limited by the stations in your area. Even in a decent city like Boston, those options are very limited. And oh god, the ads. There have been times I&amp;rsquo;ve gotten in my car and driven somewhere 15 minutes away and only heard one song and a bunch of ads. And don&amp;rsquo;t get me started on those &amp;ldquo;morning shows&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;So there are streaming services. Spotify I never really loved, but I did subscribe to Tidal for a while off and on. But those are all about building playlists. You can listen to someone else&amp;rsquo;s playlist, which is kind of like radio, but not really. And, I don&amp;rsquo;t want to pay a monthly fee. I want radio, but not just local radio.&lt;/p&gt;&#xA;&lt;h2 id=&#34;internet-radio&#34;&gt;Internet Radio&lt;/h2&gt;&#xA;&lt;p&gt;Of course I&amp;rsquo;m no stranger to internet radio. I&amp;rsquo;ve dabbled over the years. Shoutcast, Ice, Pandora, others.&lt;/p&gt;&#xA;&lt;p&gt;And I&amp;rsquo;ve been a big fan of &lt;a href=&#34;https://somafm.com/&#34;&gt;SomaFM&lt;/a&gt; for ages and regularly throw them a donation to do my bit to keep them going.&lt;/p&gt;&#xA;&lt;p&gt;A month or two I decided to go all in on Internet Radio though. I started with an Android app, that I used on an old Pixel phone hooked up via Bluetooth to my stereo. The app I got is called &lt;a href=&#34;https://www.radiomobi.net/&#34;&gt;Radio Mobi&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;radio mobi&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-23/mobi.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I started with the free version and loved it so much I upgraded immediately. I use it in the car and I use it during work and usually go to sleep listening to it. It kind of took over my music listening world. There are many thousands of stations with every language, location, genre, time period, etc. that you can imagine. I have dozens of favorite stations saved, and bounce between them as the mood hits.&lt;/p&gt;&#xA;&lt;h2 id=&#34;hardware&#34;&gt;Hardware&lt;/h2&gt;&#xA;&lt;p&gt;The app on the phone via Bluetooth into my stereo was decent, but I was ready to commit to something more permanent. I wanted a dedicated Internet Radio receiver. I spent a good week or more looking over listings and reviews and Youtube videos. And finally settled on the Ocean Digital WR-800.&lt;/p&gt;&#xA;&lt;img alt=&#34;internet radio receiver&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-23/ocean.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This device sits on top of my stereo receiver/amplifier. It has its own speakers which are so-so, but it has line out which goes into my stereo system, so I don&amp;rsquo;t use the built-in speakers. It&amp;rsquo;s on my home WIFI and I&amp;rsquo;ve got all the same stations saved as I do on Radio Mobi (give or take).&lt;/p&gt;&#xA;&lt;img alt=&#34;internet radio receiver&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-23/hifi.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;I turn it on when I start working in the morning and mute it when I&amp;rsquo;m in meetings. The stations change with my moods.&lt;/p&gt;&#xA;&lt;p&gt;On June 17 (6/17), a local college radio station had 617 Day. 617 is the telephone area code for the Boston area. So they played all local music. It was fantastic and I learned of a couple new bands and found a new favorite, &lt;a href=&#34;https://www.slothrust.com/&#34;&gt;Slothrust&lt;/a&gt; (Slo Thrust? Sloth Rust? You choose) and bought one of their albums.&lt;/p&gt;&#xA;&lt;p&gt;Many of the hardware internet receivers you&amp;rsquo;ll buy use a service called &lt;a href=&#34;https://skytune.net&#34;&gt;Skytune&lt;/a&gt;. A nice thing about this service is you can discover stations at that site, and send them to your receiver. When the radio is on, it sets up a local web server on your network where you can manage your station library, add new streams that aren&amp;rsquo;t in the Skytune network, arrange and manage them, remove stations, start or stop a station, change the volume, etc.&lt;/p&gt;&#xA;&lt;p&gt;You can do all that on the device itself, but doing it on a computer or phone is great too.&lt;/p&gt;&#xA;&lt;p&gt;The device also has:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;FM Radio. I don&amp;rsquo;t use it because A. my stereo system has a better FM receiver. B. I now listen to local stations via their stream anyway - perfect reception.&lt;/li&gt;&#xA;&lt;li&gt;SD card playback. I don&amp;rsquo;t use it because I have other solutions.&lt;/li&gt;&#xA;&lt;li&gt;Media Center, aka DLNA/UPNP. Nah.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I just use the internet radio function and it&amp;rsquo;s fantastic.&lt;/p&gt;&#xA;&lt;h2 id=&#34;just-remembered&#34;&gt;Just remembered&amp;hellip;&lt;/h2&gt;&#xA;&lt;p&gt;I just recalled that some years ago I tried this experiment with a different device. The CCrane     CC Wifi Internet Radio.&lt;/p&gt;&#xA;&lt;img alt=&#34;internet radio receiver&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2026-06-23/ccrane.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This thing is crap. Horrible UX, many unworking stations, horrible sound quality. I used it briefly than didn&amp;rsquo;t touch it for years. Tried to use it again recently and I couldn&amp;rsquo;t get a single station on it. Must have changed server configs or something. I trashed it.&lt;/p&gt;&#xA;&lt;p&gt;Weirdly, the Ocean Digital I just bought was only $10 more than the CC WIFI I bought eight years ago.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;Ditch Spotify! Discover internet radio. You won&amp;rsquo;t go back. Use the money to buy music.&lt;/p&gt;&#xA;&lt;p&gt;Actually, I don&amp;rsquo;t like telling people what they should or shouldn&amp;rsquo;t do. But I recommend trying it out. You might love it.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>XT Eink X4</title>
      <link>https://bit-101.com/blog/posts/2026-04-08/xteink-x4/</link>
      <pubDate>Wed, 08 Apr 2026 19:04:46 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-04-08/xteink-x4/</guid>
      <description>&lt;p&gt;I have been a big fan of eInk book readers for just about 17 years now. In April 2009 I got my first Kindle and had a number of follow ups over the next many years. Ten years later I moved over to the Onyx Boox line of ereaders, with a Nova Pro 7.8&amp;quot; device, then a Note Air 10.3&amp;quot; device, a Poke 3 6&amp;quot; device, which I sat on and broke about a year ago, so got a Go 6 6&amp;quot; reader to replace it. I also have a Kobo Clara color eInk device and a Remarkable eInk tablet.&lt;/p&gt;&#xA;&lt;p&gt;A month or so ago I caught wind of the XT Eink devices, in particular the X4. It&amp;rsquo;s a tiny little 4.3&amp;quot; device! I watched a bunch of videos about it and considered getting one but I didn&amp;rsquo;t really &lt;em&gt;need&lt;/em&gt; a new ereader. (See paragraph one.) But then a week ago I saw some more posts about it and got interested all over again and went for it. It goes for $69 USD, so worth a shot, right?&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.xteink.com/products/xteink-x4&#34;&gt;XT Eink X4&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;It arrived over the past weekend and I&amp;rsquo;m in love with it!&lt;/p&gt;&#xA;&lt;img alt=&#34;xt eink x4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-04-08/photo_01.jpg&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;the-bad&#34;&gt;The Bad&lt;/h2&gt;&#xA;&lt;p&gt;OK, the thing cost 70 bucks. It can&amp;rsquo;t be amazing, right? Here&amp;rsquo;s what it lacks:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;No light&lt;/li&gt;&#xA;&lt;li&gt;No touch screen&lt;/li&gt;&#xA;&lt;li&gt;No dictionary&lt;/li&gt;&#xA;&lt;li&gt;No search&lt;/li&gt;&#xA;&lt;li&gt;No audio&lt;/li&gt;&#xA;&lt;li&gt;No apps&lt;/li&gt;&#xA;&lt;li&gt;Only supports epub and txt&lt;/li&gt;&#xA;&lt;li&gt;Minimal formatting options&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If this were a general purpose reader, many of those would be big problems. But none of them bother me. This is not my every day reader for when I&amp;rsquo;m on the sofa or reading before bed. This is the thing that&amp;rsquo;s always in my pocket and if I find myself waiting in the car, or in line somewhere or whatever, rather than doom scrolling on my phone, I read a couple pages of the book I&amp;rsquo;m on.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-good&#34;&gt;The Good&lt;/h2&gt;&#xA;&lt;p&gt;Just about everything else on this is great, especially for the price.&lt;/p&gt;&#xA;&lt;h3 id=&#34;magsafe&#34;&gt;Magsafe&lt;/h3&gt;&#xA;&lt;p&gt;It&amp;rsquo;s got a Magsafe compatible magnet on the back. The marketing says it&amp;rsquo;s for sticking on the back of your phone. But I think it doesn&amp;rsquo;t really work that well with a lot of phones. Not with my Pixel 10. Just because of the camera bump - it works fine if I put it on sideways, but that&amp;rsquo;s kind of useless. But you can get a magnetic case for it and it&amp;rsquo;ll work with any Magsafe accessories.&lt;/p&gt;&#xA;&lt;p&gt;Note, it does not charge via Magsafe - only via USB-C.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-battery&#34;&gt;The Battery&lt;/h3&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t know, it came charged, I&amp;rsquo;ve had it for 5 days, used it a bunch, it&amp;rsquo;s at 93%. Very promising. As I said, charges with USB-C.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-os-and-reader-apps&#34;&gt;The OS and Reader apps&lt;/h3&gt;&#xA;&lt;p&gt;I can&amp;rsquo;t say much about the factory OS and software. I only tried it for a short while. And then&amp;hellip;&lt;/p&gt;&#xA;&lt;h3 id=&#34;crosspoint-reader&#34;&gt;Crosspoint-Reader&lt;/h3&gt;&#xA;&lt;p&gt;Everyone suggests &lt;a href=&#34;https://github.com/crosspoint-reader/crosspoint-reader&#34;&gt;Crosspoint-Reader&lt;/a&gt; which is a 3rd party, community-developed firmware and reader app. It&amp;rsquo;s great. It doesn&amp;rsquo;t have all the bells and whistles of my favorite ebook reader app, KOReader, but it does a lot. Fonts, font size, a fair amount of formatting options, cover display on sleep, status bar customization, syncing progress with KOReader (though it&amp;rsquo;s very buggy in my testing), wifi book transfer, etc.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-controls&#34;&gt;The Controls&lt;/h3&gt;&#xA;&lt;p&gt;As mentioned, it&amp;rsquo;s not touch screen, but has a power button on the left and then back/forward buttons on the side, that look like volume buttons on a phone. Then on the bottom front, there are what looks like two buttons but are really four. The two on the left are back and select, and on the right are additional back/forward buttons. Overall, pretty easy to get around with, but because it&amp;rsquo;s so tiny, and you&amp;rsquo;ll have to change pages so often, the button placement is not perfect. Using a Magsafe ring on the back to hold the device with makes this a lot better.&lt;/p&gt;&#xA;&lt;h3 id=&#34;performance&#34;&gt;Performance&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been surprised how zippy this is. It does take a few seconds to start up (like under 5 on Crosspoint) but page turning and menus are way faster than I expected on such an inexpensive, tiny device.&lt;/p&gt;&#xA;&lt;h3 id=&#34;readability&#34;&gt;Readability&lt;/h3&gt;&#xA;&lt;p&gt;The resolution is pretty low - 220 ppi. But it&amp;rsquo;s fine for reading a book. The screen is small, but you can adjust the font size to your liking. Of course, if your vision requires you to use a very large font, you&amp;rsquo;re going to be much better off with a larger screen - the Crosspoint software offers font sizes of small, medium, large, extra large. Extra large will get you a medium size paragraph on the screen. Small will get you a couple of paragraphs. The default medium is about right for me.&lt;/p&gt;&#xA;&lt;p&gt;Note that there&amp;rsquo;s no search function on either the default or the Crosspoint software. The default software has bookmarks, but oddly, Crosspoint does not. But both allow you to navigate by chapters.&lt;/p&gt;&#xA;&lt;h3 id=&#34;x3&#34;&gt;X3&lt;/h3&gt;&#xA;&lt;p&gt;Just released, or just about to release, is the next version, in reverse numerical order, the X3. It&amp;rsquo;s even smaller! 3.7&amp;quot;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.xteink.com/products/xteink-x3&#34;&gt;XT Eink X3&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;It has pogo pin charging and better button placement, shake to turn pages, NFC. $79 US. I think I&amp;rsquo;m good with the X4. It&amp;rsquo;s about as small as I&amp;rsquo;d want to go.&lt;/p&gt;&#xA;&lt;h2 id=&#34;photos&#34;&gt;Photos&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll just close out with some photos.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;xt eink x4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-04-08/photo_02.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;xt eink x4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-04-08/photo_03.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;xt eink x4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-04-08/photo_04.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;xt eink x4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-04-08/photo_05.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;xt eink x4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-04-08/photo_06.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;xt eink x4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-04-08/photo_07.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>A New Book in the Works!</title>
      <link>https://bit-101.com/blog/posts/2026-03-29/a-new-book-in-the-works/</link>
      <pubDate>Sun, 29 Mar 2026 12:10:20 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-03-29/a-new-book-in-the-works/</guid>
      <description>&lt;p&gt;Hot on the trails of &lt;a href=&#34;https://bit-101.com/codingcurves/&#34;&gt;Coding Curves&lt;/a&gt;, I&amp;rsquo;m excited to announce my next writing project&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The Creative Coding Compendium, Volume I&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Most of my books up to now have been focused on specific topics - fractals, chaos and strange attractors, or 2D curves. Or they&amp;rsquo;ve been targeted to specific coding platforms - Flash or iOS. For my next round of writing, I want to open up the flood gates and just write about creative coding. There are so many techniques I&amp;rsquo;ve learned over the years. And I&amp;rsquo;m excited to share them. I&amp;rsquo;m not trying to force any theme on them. Each chapter is standalone and has no solid connection the previous or next chapter.&lt;/p&gt;&#xA;&lt;p&gt;Furthermore, I&amp;rsquo;m going all in on the pseudocode bandwagon. I wrote about so many cool coding topics in the 2010s, but all of those books are out of print. And while the principles in them are just as useful now as when I wrote them, the languages and platforms I wrote those books for are dead. Some of the early stuff involved putting code on animation frames in the Flash timeline and looping the timeline back to the frame with the code on it repeatedly in order to achieve animation - a technique that would leave most modern programmers scratching their heads. So going forward, I wanted to write code in a language that could not die - because it doesn&amp;rsquo;t exist.&lt;/p&gt;&#xA;&lt;p&gt;A big part of the inspiration for this kind of book is Clifford Pickover&amp;rsquo;s early books, &lt;em&gt;Computers and the Imagination&lt;/em&gt;, &lt;em&gt;Computers, Pattern, Chaos and Beauty&lt;/em&gt; and &lt;em&gt;Mazes for the Mind&lt;/em&gt;. These are wonderful books filled with crazy ideas and images, with source code written in BASIC. While the code is diligently commented and described in detail in the text, it&amp;rsquo;s not always the easiest stuff for a programmer in 2026 to parse.&lt;/p&gt;&#xA;&lt;img alt=&#34;a BASIC program&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-03-29/00198.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;But it&amp;rsquo;s not impossible and it&amp;rsquo;s a lot of fun to just browse through one of his books, see an interesting image, convert the code over to some other language and start messing with it.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m hoping that this new book I&amp;rsquo;m working on, and any following it, can continue to have a long useful life ahead of them, no matter what programming looks like 20 years from now (assuming that humans are still writing code in 20 years&amp;hellip; but I digress and make myself sad.)&lt;/p&gt;&#xA;&lt;p&gt;Anyway, perhaps you&amp;rsquo;d like to see the table of contents.&lt;/p&gt;&#xA;&lt;img alt=&#34;table of contents&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-03-29/toc.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Three hundred pages of projects, lots of code and graphics, concepts, suggestions of where to take things. And then three useful appendixes covering details related to other code in the book.&lt;/p&gt;&#xA;&lt;h3 id=&#34;where-we-at&#34;&gt;Where we at?&lt;/h3&gt;&#xA;&lt;p&gt;As of a week ago, all the chapters had completed first drafts with images and working code. I gave myself a week off and today started on first round editing. Cleaning up typos and weasel words and things that I read and say to myself, &amp;ldquo;what the hell was I trying to say here?&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;When that&amp;rsquo;s all done, I&amp;rsquo;ll go through all the code and reproduce it in 2-3 different languages/frameworks. Likely vanilla JavaScript/Canvas, Processing, and Python with PyCairo. Just to make sure that I&amp;rsquo;m not telling people to do things that will be impossible in any of the more common platforms. Or at least to be able to say, &amp;ldquo;hey watch out if you&amp;rsquo;re using platform X, you&amp;rsquo;ll need to do this&amp;hellip;&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;That may result in some more copy changes. And after that I&amp;rsquo;ll go through the whole book from top to bottom at least one more time making sure it&amp;rsquo;s as good as it can be.&lt;/p&gt;&#xA;&lt;p&gt;So&amp;hellip; many more weeks of work on this. But I&amp;rsquo;m very excited about it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;and-after-that&#34;&gt;And after that?&lt;/h3&gt;&#xA;&lt;p&gt;As you may have noticed, this one has &amp;ldquo;Volume I&amp;rdquo; in the title. My initial brainstorm came up with enough subjects for at least two books, so a second volume is my intention. Maybe more? Who knows. Let me get through this one at least.&lt;/p&gt;&#xA;&lt;p&gt;Without getting too philosophical about things, as I get older I do think about what I will leave behind. Especially in a world where I suspect people won&amp;rsquo;t really be learning to code much longer. Not in the same sense that we think of &amp;ldquo;learning to code&amp;rdquo;. They&amp;rsquo;ll tell a computer what they want, and some code will be generated, which may or may not be viewed, and only partially understood if anyone does view it. So if I can leave some artifacts behind about what it was like in the old days, that will make me a little less sad about where things are headed.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, stay tuned for more updates.&lt;/p&gt;&#xA;&lt;p&gt;Here are a few sample images from the book:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-03-29/figure_04-08.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-03-29/figure_05-09.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-03-29/figure_08-20.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-03-29/figure_09-10.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-03-29/figure_10-31.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-03-29/figure_11-18.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Coding Curves Is Live!</title>
      <link>https://bit-101.com/blog/posts/2026-02-23/coding-curves-is-live/</link>
      <pubDate>Mon, 23 Feb 2026 07:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-02-23/coding-curves-is-live/</guid>
      <description>&lt;p&gt;I did it! It&amp;rsquo;s live! It&amp;rsquo;s for sale! My seventeenth book!!!&lt;/p&gt;&#xA;&lt;img alt=&#34;cover for book Coding Curves by Keith Peters&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-02-23/cover.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You can buy it on Gumroad: &lt;a href=&#34;https://bit101.gumroad.com/l/coding_curves&#34;&gt;https://bit101.gumroad.com/l/coding_curves&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s along side my last self-published book, &lt;a href=&#34;https://bit101.gumroad.com/l/playingwithchaos&#34;&gt;https://bit101.gumroad.com/l/playingwithchaos&lt;/a&gt; that was published nearly 13 years ago!&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s only $9.99 US, which is pretty cheap for a book these days. And if you buy either one, you can get the other for 40% off. Look at me being a marketer!&lt;/p&gt;&#xA;&lt;p&gt;When you buy it, you&amp;rsquo;ll get four versions of the book:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;EPUB - you can read this on just about anything, and even add it to your Kindle library and Amazon will convert it to whatever format it needs to be in to read on a Kindle.&lt;/li&gt;&#xA;&lt;li&gt;PDF - also readable just about anywhere on anything.&lt;/li&gt;&#xA;&lt;li&gt;KEPUB - this is targeted towards Kobo readers. It&amp;rsquo;s actually the exact same EPUB file, but with a different extension, which causes the Kobo device to use a different renderer which is much better than their usual EPUB renderer.&lt;/li&gt;&#xA;&lt;li&gt;AZW3 - one of the native Kindle formats. Really only useful if you want to transfer the file to your Kindle device via USB.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;All four of these are free from DRM. The EPUB was validated with &lt;a href=&#34;https://github.com/w3c/epubcheck/releases&#34;&gt;EPUBCheck&lt;/a&gt; which is a fantastic tool if you are creating EPUBs. It found a bunch of issues and I was able to fix all of them. And all the other formats were generated from the EPUB.&lt;/p&gt;&#xA;&lt;p&gt;If you want to know more about the kind of curves you&amp;rsquo;ll be creating with this book, I created a mini site to show them off:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://bit-101.com/codingcurves/&#34;&gt;https://bit-101.com/codingcurves/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;whats-next&#34;&gt;What&amp;rsquo;s next?&lt;/h2&gt;&#xA;&lt;p&gt;Getting back into serious writing was a whole lot of fun. I realized how much I&amp;rsquo;ve missed writing books. The new workflow I&amp;rsquo;ve developed makes writing and self-publishing a full book about as easy as it can be. With that said&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve already started the work on my next book, &lt;strong&gt;Coding Color&lt;/strong&gt;! This is a book about writing code to create, modify, manage, and effectively use color, color schemes, and palettes. And understanding and being able to convert between different color spaces and models. As you can see by my posts over the past few months, this is an area I&amp;rsquo;ve been doing a very deep dive into recently. I&amp;rsquo;ve learned a lot. This isn&amp;rsquo;t going to be a theoretical or philisophical book about color. It will be very hands on, writing functions that do things with color. I&amp;rsquo;m very excited about being able to immerse myself in this project now that Coding Curves is complete. I&amp;rsquo;m sure I&amp;rsquo;ll be posting more about it soon!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Coding Curves: The Book</title>
      <link>https://bit-101.com/blog/posts/2026-02-16/coding-curves-the-book/</link>
      <pubDate>Mon, 16 Feb 2026 12:00:13 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-02-16/coding-curves-the-book/</guid>
      <description>&lt;p&gt;Back in late 2022, through the start of 2023, I wrote fourteen blog posts in a series called &amp;ldquo;Coding Curves&amp;rdquo;. That&amp;rsquo;s still up on the 2017-2023 version of this blog:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://bit-101.com/2017/2022/11/coding-curves/&#34;&gt;https://bit-101.com/2017/2022/11/coding-curves/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;My intention was always to compile these into a book. I&amp;rsquo;d wanted to write this book for a LONG time, and started it multiple times. Only by writing it post by post and publishing it piecemeal was I able to get it out. But three years went by and the book form never emerged. Part of it was plain inertia and procrastination, but there were also technical hurdles.&lt;/p&gt;&#xA;&lt;p&gt;One issue was that the original posts were written in WordPress. Converting that to EPUB/PDF meant probably converting it to markdown first, then running it through &lt;code&gt;pandoc&lt;/code&gt; to create the final book. Or, I could grab the HTML that WordPress generated and try to use &lt;code&gt;pandoc&lt;/code&gt; on that, but There was SO MUCH excess HTML and CSS added, that I didn&amp;rsquo;t feel great about that.&lt;/p&gt;&#xA;&lt;p&gt;But since then I&amp;rsquo;d converted that whole site to markdown and used Hugo to publish it as a static site. So now I had the markdown. It still needed a lot of cleanup, but it was doable.&lt;/p&gt;&#xA;&lt;p&gt;The second big issue was that, while I&amp;rsquo;d previously self-published &lt;a href=&#34;https://bit-101.com/playingwithchaos&#34;&gt;Playing With Chaos&lt;/a&gt; using &lt;code&gt;pandoc&lt;/code&gt;, my build process was really cumbersome, and 13 years out of date. I hesitate to admit this, but the book build was written in Ant! You remember Ant? It&amp;rsquo;s basically &amp;ldquo;make&amp;rdquo; or &amp;ldquo;just&amp;rdquo; but the build file is written in XML. Yikes!&lt;/p&gt;&#xA;&lt;p&gt;It was also very tailored to creating books for publishing on Amazon Kindle. It used a proprietary &lt;code&gt;kindlegen.exe&lt;/code&gt; command line app (yes, Windows) and a handful of custom Python scripts to massage the table of contents into shape. I really didn&amp;rsquo;t want to look at this.&lt;/p&gt;&#xA;&lt;p&gt;But when I finally got up the courage, I found that I could strip it way, way down. In fact, I could do the entire build with just &lt;code&gt;pandoc&lt;/code&gt;. It was just a matter of passing all the markdown files to &lt;code&gt;pandoc&lt;/code&gt; and outputting the result to an EPUB. There were a few command line switches for things like generating a table of contents, adding a cover image, embedded font, and CSS file. But no other scripts or tools in the toolchain. Great. (Needless to say, I am no longer using Ant, either. Moved it over to &lt;code&gt;just&lt;/code&gt;.)&lt;/p&gt;&#xA;&lt;p&gt;Cleaning up the markdown was a pain. All the images carried through the original WordPress image tags that had multiple sizes of the same image listed. And a ton of punctuation characters - quotes, dashes, greater than, less than, etc. - were expanded to HTML entities. I became somewhat of an expert with &lt;code&gt;sed&lt;/code&gt; which allowed me to clean all of that up across all of the chapter files. But even after that, it took a lot of manual clean up to get it into shape. And then I had to grab all the right images from the site and put them in the book folder and make sure they were all correctly linked.&lt;/p&gt;&#xA;&lt;p&gt;All that was just clean up. After that began the editing.&lt;/p&gt;&#xA;&lt;p&gt;First, spell check, grammar check, my own custom script to find &amp;ldquo;weasel words&amp;rdquo; - cliches that don&amp;rsquo;t add any value to the text.&lt;/p&gt;&#xA;&lt;p&gt;And checking all the pseudocode to make sure it was correct. To do this, I made a little app that allowed me to paste in the pseudocode from the book and with relatively few adjustments, let me run it in Go using my own existing libraries. I&amp;rsquo;m not talking about transcoding or transpiling. I made function definitions that matched the pseudocode functions. These were wrappers for the real functionality in my library. For example, here are a few:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Golang&#34; data-lang=&#34;Golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;canvas&lt;/span&gt;(w,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;h&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;width&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;w&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;height&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;h&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;cairo.&lt;span style=&#34;color:#c0f&#34;&gt;CreateContext&lt;/span&gt;(w,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;h)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;ClearWhite&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;SetSourceBlack&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;SetLineWidth&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;lineTo&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;LineTo&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;moveTo&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;MoveTo&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;stroke&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Stroke&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I also made a bunch of aliases to things, and &amp;ldquo;global&amp;rdquo; vars.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Golang&#34; data-lang=&#34;Golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;var&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;cos&lt;span style=&#34;color:#bbb&#34;&gt;           &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Cos&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;sin&lt;span style=&#34;color:#bbb&#34;&gt;           &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Sin&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;tan&lt;span style=&#34;color:#bbb&#34;&gt;           &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Tan&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;sqrt&lt;span style=&#34;color:#bbb&#34;&gt;          &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Sqrt&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;atan2&lt;span style=&#34;color:#bbb&#34;&gt;         &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Atan2&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;acos&lt;span style=&#34;color:#bbb&#34;&gt;          &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Acos&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context&lt;span style=&#34;color:#bbb&#34;&gt;       &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, I could paste in a pseudocode example straight from the book, like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Golang&#34; data-lang=&#34;Golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;canvas&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;moveTo&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;lineTo&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;700&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;700&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;stroke&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that would just work!&lt;/p&gt;&#xA;&lt;p&gt;All this saved me from having to re-write each snippet in some other language in order to test it. I could actually run most of the pseudocode as-is.&lt;/p&gt;&#xA;&lt;p&gt;After all that, I just had a lot of re-writing to do. Making it read more like a book, not a bunch of blog posts. Removed extraneous passages and awkward wording, added better examples and explanations where needed, generated new code samples and images where called for. In some cases, I rewrote almost entire chapters, with all new code and images.&lt;/p&gt;&#xA;&lt;p&gt;I also removed all the animation examples, sadly. I imagine there are ways to embed animations in &lt;em&gt;some&lt;/em&gt; ebook formats, but I didn&amp;rsquo;t want to go there.&lt;/p&gt;&#xA;&lt;p&gt;All of this took several weeks. Whole weekends would fly by when that&amp;rsquo;s almost all I did. But I&amp;rsquo;m at the end of it. It&amp;rsquo;s in great shape, but I&amp;rsquo;m going through one more pass of the whole book cover to cover and making sure everything is clear and sounds right. I hope to get through that in another week or so.&lt;/p&gt;&#xA;&lt;p&gt;I considered adding a few more chapters. There are some topics I think would fit in well - stuff I discovered in the past few years. But I decided to release it as-is. As an ebook, I can easily add more chapters later and release an updated version.&lt;/p&gt;&#xA;&lt;p&gt;The plan is to release it on Gumroad, alongside &lt;a href=&#34;https://bit101.gumroad.com/l/playingwithchaos&#34;&gt;Playing With Chaos&lt;/a&gt;. DRM-free, EPUB, PDF and AZW3. Watch this space.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Isometric Engine 2026 - Part I</title>
      <link>https://bit-101.com/blog/posts/2026-01-25/isometric-engine-2026-part-i/</link>
      <pubDate>Sun, 25 Jan 2026 10:32:18 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-01-25/isometric-engine-2026-part-i/</guid>
      <description>&lt;p&gt;Isometric drawing is always a fun thing to work on. I say always because I&amp;rsquo;ve created many isometric frameworks over the past 20+ years. I think I wrote a book chapter or two about it, and published a couple of Coding Math Videos about it &amp;hellip; 10 years ago (OMG):&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=go1qrWFw_bs&#34;&gt;Coding Math: Episode 41 - Isometric 3D Part I&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=CI7VME4U95E&#34;&gt;Coding Math: Episode 42 - Isometric 3D Part II&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In those videos I discussed the difference between true isometric, where everything is &amp;ldquo;one angle&amp;rdquo; and dimetric, where the width and height of a tile are generaly in a 2:1 ratio. I drew some tiles manually, and loaded in prerendered tile sets and drew those all in various locations. I used the dimetric version, as for that kind of pixel art, it works out better (as described in the videos).&lt;/p&gt;&#xA;&lt;p&gt;All that&amp;rsquo;s good but in recent years I&amp;rsquo;ve been taking a very different approach to doing isometric. Rather than just drawing flat faces, or pre-rendered isometric tiles that someone has hand-drawn - usually at a fixed size - I wanted to draw isometric shapes of varying sizes, with dynamic, high resolution textures, with (somewhat) dynamic shading. I&amp;rsquo;ve made a few attempts at this, and while it generally works pretty well, I generally got hung up on the API or interface to creating an isometric box, sizing it, positioning it, assigning textures for each of its three faces. For a single box, that&amp;rsquo;s not too bad, but when you have a bunch of boxes, and you want each one to have a different color and texture&amp;hellip; it gets crazy fast. And the last few iterations I did on this were just so frustrating to use that I never used them.&lt;/p&gt;&#xA;&lt;p&gt;But my most recent attempt is promising and I&amp;rsquo;ve been having a ton of fun cranking out random images with it over the past few days.&lt;/p&gt;&#xA;&lt;p&gt;And my recent color palette work has really paid off in making those images look better than they would have.&lt;/p&gt;&#xA;&lt;h2 id=&#34;drawing-a-box&#34;&gt;Drawing a box&lt;/h2&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s start drawing a single box in a fixed location with a fixed size and fixed coloring. I&amp;rsquo;m going to be doing true isometric rendering here, meaning that our angles and sizes for each face are going to be the same. Here&amp;rsquo;s a simple box:&lt;/p&gt;&#xA;&lt;img alt=&#34;simple isometric box&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;It&amp;rsquo;s composed of three identical diamond shapes. The obtuse angles are all 120 degrees and the acute angles are all 60 degrees. Every line drawn there is exactly the same length. Hence, isometric - &amp;ldquo;one measure&amp;rdquo;. Also, if you removed the internal lines, you have a perfect regular hexagon. The world needs more hexagons.&lt;/p&gt;&#xA;&lt;p&gt;Now, I want to draw a &amp;ldquo;texture&amp;rdquo; on each one of these faces. Don&amp;rsquo;t get caught up in the word &amp;ldquo;texture&amp;rdquo;. I just want to draw something there, a flat color, a pattern, random noise, whatever. But I want whatever I draw there to look like it was actually sitting on that face of the box. For example, I don&amp;rsquo;t want to draw a circle like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;simple isometric box with a circle on one face&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Yuck! No! I want it to look like there&amp;rsquo;s a circle on that face of the box and it&amp;rsquo;s rotated and skewed like the face itself. Consider that the face of that box is represented as a square - the way you would see it if you were to walk round to the right and crouch down so you were looking at it head on.&lt;/p&gt;&#xA;&lt;p&gt;So, how do we distort that square, and whatever is in it, to look like the isometric box face we see?&lt;/p&gt;&#xA;&lt;p&gt;The answer: with 2D affine transforms. Enter the Matrix!&lt;/p&gt;&#xA;&lt;p&gt;First, I&amp;rsquo;ll just draw the untransformed face. A 300x300 pixel square filled with gray and a black, 100-pixel radius circle inside of it. Its origin point is the center of the box. I know it&amp;rsquo;s 300x300 because I made the isometric box with sides of 300 pixels.&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with a square with a circle in it hanging off one side&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now I need to kind of skew the box up and a bit to the left to make it fit in that face. Through a bit of analytical reasoning, working out the math, and a WHOLE LOT of trial and error, I came up with a transformation matrix that I can apply to the canvas before drawing that puts that face right into place. Here it is:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[sin(pi/3) -0.5]&#xA;[    0       1 ]&#xA;[    0       0 ]&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most drawing APIs have a way to set the transformation matrix of a canvas or context object. In JavaScript for example, you&amp;rsquo;d do it like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.transform(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.sin(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.PI&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;), &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I think in Processing, it looks like:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;applyMatrix(sin(PI/3), -0.5, 0, 1, 0, 0)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;m using a Go wrapper for &lt;a href=&#34;https://www.cairographics.org/&#34;&gt;CairoGraphics&lt;/a&gt; with a whole ton of custom functions and currently it looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;TransformValues&lt;/span&gt;(math.&lt;span style=&#34;color:#c0f&#34;&gt;Sin&lt;/span&gt;(pi&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;),&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the rest of this series I&amp;rsquo;ll pretent I&amp;rsquo;m using JavaScript.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, if you apply that matrix just before drawing the square/circle shape in the last image, you get:&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with a square with a circle in it on one side&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Voila! Perfect! To be fair, this was the simplest one, but let&amp;rsquo;s do the rest!&lt;/p&gt;&#xA;&lt;p&gt;Hot tip: make sure you save the state of your context before you do each set of transforms, and restore it before doing the next set. Otherwise, you&amp;rsquo;ll have multiple transforms layerd on top of each other and you&amp;rsquo;ll get very confused.&lt;/p&gt;&#xA;&lt;p&gt;Starting with the left face, I&amp;rsquo;ll draw another slightly different &amp;ldquo;texture&amp;rdquo; and then transform it. First, the untransformed square:&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with a square with a circle in it on one side, and another pink square with a red circle hanging off the side&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now, I could flip that box around that center line and then skew it upwards, but say my texture had some text in it, or something else I did not want to appear backwards. So what I&amp;rsquo;ll do is first slide it up to the left by 300 pixels using a translate, something like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.translate(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we are here:&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with a square with a circle in it on one side, and another pink square with a red circle hanging off the left side&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now we just need to skew it up. Here would be the code for that:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.transform(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.sin(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.PI&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;), &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s essentially the same transform we used for the right side, but with a positive 0.5 instead of a negative one this time. This causes it to skew from the opposite side.&lt;/p&gt;&#xA;&lt;p&gt;Now if we add that transform after the translate, we get:&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with a square with a circle in it on one side, and another pink square with a red circle on the other side, but misaligned&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s obviously wrong. It has to do with the order of matrix operations. This is something that it takes wrapping your head around. Often you have to apply them in what seems to the brain to be the inverse order of what you want to do. If we do the transform and then the translate, like so&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.transform(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.sin(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.PI&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;), &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.translate(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then we are golden!&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with a square with a circle in it on one side, and another pink square with a red circle on the other side&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Side note, there are ways to combine multiple transforms into a single matrix, but I&amp;rsquo;m fine with making two or three calls. In any case, you&amp;rsquo;d have to make two or three calls to assemble the final matrix anyway, so I&amp;rsquo;m leaving it as is.&lt;/p&gt;&#xA;&lt;p&gt;Other side note: earlier I had referenced the &lt;code&gt;setTransform&lt;/code&gt; method, but in this case you want to use the &lt;code&gt;transform&lt;/code&gt; method. The difference here is that &lt;code&gt;transform&lt;/code&gt; will alter the existing transform, adding a new transform to it, whereas &lt;code&gt;setTransform&lt;/code&gt; will remove any previous transform that had been applied and then transform the context as specified. Make sure you know the difference between these two and use the right one.&lt;/p&gt;&#xA;&lt;p&gt;Onto the final side, the top! I&amp;rsquo;ll make another face, untransformed at first.&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with circles on two sides and another square with a circle hanging off the side&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_09.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here, I labeled one corner, &amp;ldquo;top left&amp;rdquo; to make a point. With the right face, it was a simple skew upwards. With the left face it&amp;rsquo;s pretty obvious that we wanted to slide the face over and skew it up, to keep the same orientation we had. But on the top, you have a choice. How should that top face be aligned? With the top left corner on the left side, or at the top? I think putting it on the right or the bottom would make it look upside down, but the first two choices seem equally arguable. I&amp;rsquo;m going to put it at the left side.&lt;/p&gt;&#xA;&lt;p&gt;I won&amp;rsquo;t go through it step by step, but here are the transforms I worked out:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.rotate(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.PI &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.transform(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.sin(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.PI&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;), &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.translate(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are probably other ways to do it, but these, in this order, give us this:&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with circles on all three sides&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_10.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;At this point, I&amp;rsquo;ve removed the original line drawing, so all you&amp;rsquo;re seeing is the three faces.&lt;/p&gt;&#xA;&lt;p&gt;If you want the top left to be up in the top, you can use this set of transforms:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.rotate(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.PI &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.transform(math.Sin(&lt;span style=&#34;color:#366&#34;&gt;Math&lt;/span&gt;.PI&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;), &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.translate(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;isometric box with circles on all three sides&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_11.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now, for every box, for every face, I can just draw to it like I was drawing to a regular 300x300 pixel canvas. Here, I just drew three different textures, that I already had sitting around in my library:&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with different textures on each side&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_12.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I can even load in images to apply to each side&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;isometric box with images on all three sides&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-25/image_13.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;end-of-part-i&#34;&gt;End of Part I&lt;/h2&gt;&#xA;&lt;p&gt;OK, that&amp;rsquo;s pretty good for a first post. In future posts, I&amp;rsquo;ll cover lighting/shading, sizing, positioning and a few other things.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>We Have Comments!</title>
      <link>https://bit-101.com/blog/posts/2026-01-22/we-have-comments/</link>
      <pubDate>Thu, 22 Jan 2026 20:06:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-01-22/we-have-comments/</guid>
      <description>&lt;p&gt;After &lt;a href=&#34;https://bit-101.com/blog/posts/2026-01-19/search/&#34;&gt;getting search working&lt;/a&gt; here (and on my other blogs), I was pretty excited. I started thinking, &amp;ldquo;what else can I do here?&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;The one last thing I missed from leaving Wordpress was comments. I looked at a whole bunch of commenting systems. All the ones here: &lt;a href=&#34;https://gohugo.io/content-management/comments/&#34;&gt;https://gohugo.io/content-management/comments/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;I discovered that Hugo even has a Disqus comment template built in. I got that one working as a test, but didn&amp;rsquo;t like it visually at all, and honestly, just kind of had a bad feeling about Disqus in general. It didn&amp;rsquo;t help that Disqus wouldn&amp;rsquo;t even load if you have tracking prevention turned on.&lt;/p&gt;&#xA;&lt;p&gt;I tried a few others, not much success. The one other one I got working also didn&amp;rsquo;t look good.&lt;/p&gt;&#xA;&lt;p&gt;The rest were either paid services, which was a path I wasn&amp;rsquo;t going to go down, required extensive server side set up, or were tied to services I don&amp;rsquo;t use - Matrix, Discourse, github.&lt;/p&gt;&#xA;&lt;p&gt;Then someone on Mastodon suggested I use Mastodon/Fediverse to handle comments. This was kind of in the back of my mind, but I didn&amp;rsquo;t really have a good idea how that would work. This post was given as a reference:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://beej.us/blog/data/mastodon-comments/&#34;&gt;https://beej.us/blog/data/mastodon-comments/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This really clicked. Great post! The author built his implementation for his own private static site generator, which is impressive. I built mine for Hugo. The main thing I needed was the fact that given a Mastodon post ID, you can load all the info about that post as JSON. He was doing it with env vars and tokens in his source files that get replaced with the vars during the site build. I had some ideas on how I could do that a bit differently in Hugo. Anyway, with the knowledge from that post, I was off to the races.&lt;/p&gt;&#xA;&lt;h2 id=&#34;my-implementation&#34;&gt;My implementation&lt;/h2&gt;&#xA;&lt;p&gt;Hugo has a heirarchy of templates and partial templates and markdown files with headers and params. I needed to figure out how to get all that in there.&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-build-the-comment-ui&#34;&gt;1. Build the comment UI.&lt;/h3&gt;&#xA;&lt;p&gt;This is straight up JS and HTML and CSS. I created a div and some JS to load and parse the JSON and turn it into styled HTML elements. I did all this at first in a simple HTML page, totally unconnected from my site.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;Comments&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;link&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;rel&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;styles/main.css&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;Comments!&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;comments_div&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;src/main.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the JS that this loaded:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; id &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;115939969899573885&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; load_comments() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; url &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;`https://mstdn.social/api/v1/statuses/&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;id&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;/context`&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;try&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; response &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;await&lt;/span&gt; fetch(url);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#555&#34;&gt;!&lt;/span&gt;response.ok) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;`Response status: &lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;response.status&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; json &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;await&lt;/span&gt; response.json();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    populate_comments(json);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;catch&lt;/span&gt; (error) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    alert(&lt;span style=&#34;color:#c30&#34;&gt;`Error loading comments: &lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;error.message&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    console.error(error.message);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; populate_comments(json) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; commentsDiv &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;document&lt;/span&gt;.querySelectorAll(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#comments_div&amp;#34;&lt;/span&gt;)[&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; posts &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; json[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;descendants&amp;#34;&lt;/span&gt;];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; posts.length; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; post &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; posts[i];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; postDiv &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;document&lt;/span&gt;.createElement(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;div&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    postDiv.innerHTML &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;`&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;post.account.acct&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;post.content&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    commentsDiv.appendChild(postDiv);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;load_comments&lt;/code&gt; function was straight from the post above. The &lt;code&gt;populate_comments&lt;/code&gt; part I just threw together. With a bit of styling copied over from this blog, I got this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-22/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I was pretty happy with this as a proof of concept. Tested different post IDs and it was all good.&lt;/p&gt;&#xA;&lt;p&gt;The one thing to note here is that it&amp;rsquo;s just displaying the responses to the original post I&amp;rsquo;m using. It doesn&amp;rsquo;t display that original post. But I fixed that later.&lt;/p&gt;&#xA;&lt;p&gt;Onto the next step.&lt;/p&gt;&#xA;&lt;h3 id=&#34;2-figure-out-how-to-pass-the-mastodon-id-to-the-code&#34;&gt;2. Figure out how to pass the Mastodon ID to the code.&lt;/h3&gt;&#xA;&lt;p&gt;I couldn&amp;rsquo;t just hard code the Mastodon ID like that. It had to be tied to each individual blog post. In Hugo, each post or page has metadata in a header. You can add your own parameters there. Here&amp;rsquo;s the header of this very post:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;We Have Comments!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;date&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;2026-01-22T20:06:00&lt;span style=&#34;color:#f60&#34;&gt;-05&lt;/span&gt;:&lt;span style=&#34;color:#f60&#34;&gt;00&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;draft&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;false&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;postImage&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;summary&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;OMG we have comments, via Mastodon!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;tags&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;info&amp;#34;&lt;/span&gt;]&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;mastodonId&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;115941984813394822&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, I added a &lt;code&gt;mastodonId&lt;/code&gt; parameter there. Now we have a bit of a catch-22 here. I need to create a Mastodon post about the blog post in order to get an ID to put in the blog post. But the Mastodon post needs to link to the blog post too!&lt;/p&gt;&#xA;&lt;p&gt;&lt;del&gt;So what I did was:&lt;/del&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;del&gt;1. Created a &lt;em&gt;private&lt;/em&gt; post on Mastodon, grabbed the ID and put it in the post.&lt;/del&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;del&gt;2. Then finished the post, published it, and got the URL.&lt;/del&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;del&gt;3. Finally, edited the Mastodon post, added the URL and made it public.&lt;/del&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;del&gt;In all transparency, I&amp;rsquo;m writing this in past tense, but as I&amp;rsquo;m writing it I&amp;rsquo;ve only done the first step and I&amp;rsquo;m finishing the post now and hoping it all works out in the long run! But when you read it, it will all be in the correct tense. Messing with my head a little bit. And I&amp;rsquo;m hoping it works as expected.&lt;/del&gt;&lt;/p&gt;&#xA;&lt;h4 id=&#34;update&#34;&gt;Update:&lt;/h4&gt;&#xA;&lt;p&gt;That didn&amp;rsquo;t work. I guess you can&amp;rsquo;t turn a private Mastodon post into a public post. So for now there&amp;rsquo;s going to have to be a bit of back and forth.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Publish the blog post with no &lt;code&gt;mastodonId&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Create a Mastodon post with the link to the blog post&lt;/li&gt;&#xA;&lt;li&gt;Copy the mastodonId into the post and republish.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If anyone has an idea for a workaround for that, let me know.&lt;/p&gt;&#xA;&lt;h3 id=&#34;3-create-the-ui-for-hugo&#34;&gt;3. Create the UI for Hugo.&lt;/h3&gt;&#xA;&lt;p&gt;This is done as a &amp;ldquo;partial&amp;rdquo;, which is just a partial HTML template that can be added to other HTML templates. Here&amp;rsquo;s the &lt;code&gt;comments.html&lt;/code&gt; partial:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ if .Params.mastodonId }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;Comments!&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;comments_div&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;https://mstdn.social/@bit101/{{ .Params.mastodonId }}&amp;#34;&lt;/span&gt;&amp;gt;Comment or read on Mastodon &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;mastodon icon&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/blog/icons/mastodon.png&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;align&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;top&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;social-icon&amp;#34;&lt;/span&gt;/&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/blog/js/comments.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  load_comments({{ .Params.mastodonId }});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }} &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, it&amp;rsquo;s all in an if block, so if I don&amp;rsquo;t add a &lt;code&gt;mastdonId&lt;/code&gt; to a post, no comment UI will show up at all.&lt;/p&gt;&#xA;&lt;p&gt;This partial will be passed a context object that contains the metadata params I described above. So &lt;code&gt;.Params.mastodonId&lt;/code&gt; will contain the ID I added. This gets passed to the &lt;code&gt;load_comments&lt;/code&gt; function.&lt;/p&gt;&#xA;&lt;h3 id=&#34;4-update-the-javascript&#34;&gt;4. Update the JavaScript.&lt;/h3&gt;&#xA;&lt;p&gt;I updated the script that loads the comments. As mentioned earlier, loading the post &lt;em&gt;context&lt;/em&gt; with &lt;code&gt;https://&amp;lt;mastodon.server&amp;gt;/api/v1/statuses/&amp;lt;ID&amp;gt;/context&lt;/code&gt; gives you the data around a particular post - any posts it is a child of (ancestors) and any child posts of this post (descendants). But it doesn&amp;rsquo;t give you any info about the post itself. So I had to make two calls - one to load the initial post that I made, and another to load the descendents.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the final JS:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; load_comments(id) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; contextURL &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;`https://mstdn.social/api/v1/statuses/&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;id&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;/context`&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; postURL &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;`https://mstdn.social/api/v1/statuses/&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;id&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;try&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; postJSON &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;await&lt;/span&gt; getJSON(postURL);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; contextJSON &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;await&lt;/span&gt; getJSON(contextURL);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    populate_comments(postJSON, contextJSON, id);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;catch&lt;/span&gt; (error) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    console.error(error.message);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; getJSON(url) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; response &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;await&lt;/span&gt; fetch(url);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#555&#34;&gt;!&lt;/span&gt;response.ok) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;`Response status: &lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;response.status&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;await&lt;/span&gt; response.json();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; populate_comments(postJSON, contextJSON, id) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; commentsDiv &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;document&lt;/span&gt;.querySelectorAll(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#comments_div&amp;#34;&lt;/span&gt;)[&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// original post&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; postDiv &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;document&lt;/span&gt;.createElement(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;div&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  postDiv.innerHTML &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;`&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;postJSON.account.acct&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;postJSON.content&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  commentsDiv.appendChild(postDiv);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// replies&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; posts &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; contextJSON.descendants;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; posts.length; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; post &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; posts[i];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;const&lt;/span&gt; postDiv &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;document&lt;/span&gt;.createElement(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;div&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    postDiv.innerHTML &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;`&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;post.account.acct&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;post.content&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    commentsDiv.appendChild(postDiv);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If anything goes wrong, I just log it to the console and carry on as if nothing had happened. The end user won&amp;rsquo;t be able to fix it anyway.&lt;/p&gt;&#xA;&lt;h3 id=&#34;5-pulling-it-all-together&#34;&gt;5. Pulling it all together.&lt;/h3&gt;&#xA;&lt;p&gt;I wanted the comment section to go on any post that has a &lt;code&gt;mastdonId&lt;/code&gt;. These are rendered with a template called &lt;code&gt;single.html&lt;/code&gt;. Here&amp;rsquo;s the tail end of that:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ .Page.Next.Permalink }}&amp;#34;&lt;/span&gt;&amp;gt;Next Post »&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;hr&lt;/span&gt;/&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ partial &amp;#34;comments.html&amp;#34; . }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see, at the end of the single page, I add a horizontal rule and then load the &lt;code&gt;comments.html&lt;/code&gt; partial. The dot there is to pass the post context, which is how we get &lt;code&gt;.Params.mastdonId&lt;/code&gt; passed to the partial and eventually the JavaScript.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m pretty excited about this. Again, I&amp;rsquo;m writing this like it&amp;rsquo;s already done, but as I wrap up this post, then I&amp;rsquo;ll be able to test it all live. Crossing fingers&amp;hellip;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>2003 Blog in Hugo</title>
      <link>https://bit-101.com/blog/posts/2026-01-20/2003-blog-in-hugo/</link>
      <pubDate>Tue, 20 Jan 2026 23:02:41 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-01-20/2003-blog-in-hugo/</guid>
      <description>&lt;p&gt;My first version of this blog was put up in 2005. I think it was on the &amp;ldquo;Blogger&amp;rdquo; platform. And I think I moved to something else, then finally to Wordpress.&lt;/p&gt;&#xA;&lt;p&gt;In 2017, I had over 1,200 posts and the database was getting creaky. Most of the content was Flash-based and I hadn&amp;rsquo;t touched Flash in years, so I started a new Wordpress site.&lt;/p&gt;&#xA;&lt;p&gt;In late 2023, I was so fed up with Wordpress, I started this current version of the blog, as a static site, using Hugo.&lt;/p&gt;&#xA;&lt;p&gt;Some time in 2024 or 2025 I took the 2017 blog and converted it into a Hugo site. That had about 140 posts in it. Not too bad.&lt;/p&gt;&#xA;&lt;p&gt;I always intended to do the same to the 2003 blog. But as I said, it had over 1,200 posts. Sounded scary. But I sat down and got it done tonight. And it&amp;rsquo;s now live here:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://bit-101.com/2003&#34;&gt;https://bit-101.com/2003&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Not much of the content is relevant anymore, and most of the links are broken. But I&amp;rsquo;ve had fun going through it again.&lt;/p&gt;&#xA;&lt;p&gt;I also implemented search in all three versions of the blog, so it&amp;rsquo;s easy to find old content. That&amp;rsquo;s all.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>We have search!</title>
      <link>https://bit-101.com/blog/posts/2026-01-19/search/</link>
      <pubDate>Mon, 19 Jan 2026 21:35:51 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-01-19/search/</guid>
      <description>&lt;p&gt;I love Hugo. I have negative regrets on switching over from Wordpress some years ago. But there are a couple things I&amp;rsquo;ve missed.&lt;/p&gt;&#xA;&lt;p&gt;One is comments. Well, mixed feelings about that. I miss the interactivity. I do not miss the constant battle with spam.&lt;/p&gt;&#xA;&lt;p&gt;The other is search. Hugo is a static site generator so there is no search functionality built in. Today I started looking around for how I could add it. There are a number of solutions. The first two I tried just didn&amp;rsquo;t work. Several required multiple JavaScript libraries and other files in order to do all the search on the client side. And you have to manually place all these files in the right place and then edit to them to configure things. And many of these seemed like hobby projects.&lt;/p&gt;&#xA;&lt;p&gt;But eventually I found &lt;a href=&#34;https://pagefind.app/&#34;&gt;Pagefind&lt;/a&gt;. It seems pretty solid.&lt;/p&gt;&#xA;&lt;p&gt;Lowdown on how it works:&lt;/p&gt;&#xA;&lt;p&gt;After building your site, you run the pagefind executable, which builds an index of your site and adds it to your public site directory. It also automatically pushes the needed front end files into your site.&lt;/p&gt;&#xA;&lt;p&gt;You need a small snippet somewhere on your site that will create the search UI, including the results. I just created a new page for that, with a link in the main menu to the search page.&lt;/p&gt;&#xA;&lt;p&gt;It took a bit of back and forth to get it working, mainly due to my rustiness with Hugo itself. I think Pagefind itself worked fine right away.&lt;/p&gt;&#xA;&lt;p&gt;Then you can configure various options on what parts of your site and what parts of each page to search, and what parts to ignore, adding some metadata to pages so it knows where to get the page title, etc. All of this was more just tweaking it to get all the right results and get them to look right.&lt;/p&gt;&#xA;&lt;p&gt;For example, it was indexing my list pages - lists of posts and tags. I had to figure out how to exclude them. Wasn&amp;rsquo;t too tough. And it was showing a Mastdon logo on each post, which I guess was just the first image it found, somehow. So I turned off showing images in the results, which is fine. And it was showing &amp;ldquo;BIT-101&amp;rdquo; as the title for each page result, because it looks for the first H1 tag, which is the title of the whole site. So I had to tag the post title as being the title to index.&lt;/p&gt;&#xA;&lt;p&gt;All seems to be working pretty well now. Anyway, no need to say more. It&amp;rsquo;s right up there in the menu. Try it out. I&amp;rsquo;m really happy to have this functionality back!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Palette Visualization</title>
      <link>https://bit-101.com/blog/posts/2026-01-18/palette-visualization/</link>
      <pubDate>Sun, 18 Jan 2026 10:14:03 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-01-18/palette-visualization/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been doubling down on exploring color over the last couple of weeks. In my &lt;a href=&#34;https://bit-101.com/blog/posts/2026-01-11/getpal/&#34;&gt;last post&lt;/a&gt;, I described a new app I created to extract a color palette from an image. One thing I mentioned there was that RGB is probably not the best color space to represent colors in when trying to cluster similar colors, as it was not designed with human perception in mind. In this post, I manage to show this visually and come up with a better solution that makes extracting palettes even better. And even to a fancy trick with visualizing palettes. This might be a long one, so strap in.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-question&#34;&gt;The Question&lt;/h2&gt;&#xA;&lt;p&gt;My palette extraction code was working pretty well. To recap, I was taking each pixel&amp;rsquo;s RGB values and converting them to 3D coordinates between -0.5 and 0.5 on each axis. The red channel was mapped to the x-axis, green to the y-axis and blue to z. With all the pixels plotted in that 3D RGB space, I applied k-means clustering to find however many clusters I wanted the palette to be. As I said, it was working pretty well, actually miles ahead of everything else I&amp;rsquo;d tried, but all too often it was missing colors I thought should be in there.&lt;/p&gt;&#xA;&lt;p&gt;And I didn&amp;rsquo;t know exactly why. I had some theories:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;That RGB was not a good color space to match human color perception.&lt;/li&gt;&#xA;&lt;li&gt;That the math of clustering RGB colors tended to group colors that the human eye might see as much different.&lt;/li&gt;&#xA;&lt;li&gt;That nearby contrasting colors would make us see certain colors differently or more strongly - an observation that wasn&amp;rsquo;t supported by the numbers.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In short though, I just didn&amp;rsquo;t know exactly what the algorithm was seeing when it looked at these points. But, hey, I&amp;rsquo;ve been rendering points in 3D for nearly three decades now. So why don&amp;rsquo;t I whip something up and take a look?&lt;/p&gt;&#xA;&lt;h2 id=&#34;visualizing-rgb-space&#34;&gt;Visualizing RGB Space&lt;/h2&gt;&#xA;&lt;p&gt;As I said, I&amp;rsquo;ve been around the block on projecting 3D points so it wasn&amp;rsquo;t hard to put this together. I just had to take the RGB coordinates of each color (which ranged from -0.5 to 0.5 on each axis), scale them, apply some perspective, and then plot a point in that color. Once I had that down, I&amp;rsquo;d be able to rotate, translate, or scale them as much as I wanted, and create an animation. Though technically I considered this pretty simple, I was blown away by what it showed me. I&amp;rsquo;ll show off a few examples of some differently colored images to see how it visualizes things.&lt;/p&gt;&#xA;&lt;p&gt;Some of the images I used a lot for testing were pictures of paintings done by two of my good friends, Todd Anderson (&lt;a href=&#34;https://mstdn.social/@toddanderson&#34;&gt;https://mstdn.social/@toddanderson&lt;/a&gt;) and Jeremy Osborn (&lt;a href=&#34;https://mastodon.art/@jeremyosborn)&#34;&gt;https://mastodon.art/@jeremyosborn)&lt;/a&gt;. They are both talented watercolor artists who post often on Mastodon, each with their own distinctive style and color choices, so it was really useful to see how the algorithm handled their artistic styles. I also grabbed a few images using my &lt;a href=&#34;https://bit-101.com/blog/posts/2025-12-31/getimage/&#34;&gt;getimage&lt;/a&gt; tool, and generated some algorithmically as well. First, a painting by Todd.&lt;/p&gt;&#xA;&lt;img alt=&#34;painting of some red buildings&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/todd.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here, Todd uses  palette with lots of browns and reds as well as some contrasting slate blue and grays. Taking all the pixels in this image and plotting them in a 3D RGB space gives us this:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/todd_rgb.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;As described earlier, one corner of the cube is black, with the red, green and blue channels moving off on each side. The corner opposite to the black one would be full RGB, or white. I&amp;rsquo;ve discovered that this kind of distribution is common for visualizing images in the RGB space, a shape that starts at or near the black corner and sweeps up to the white corner, with the stronger colors forming arcs off to one or more of the sides.&lt;/p&gt;&#xA;&lt;p&gt;Here, I&amp;rsquo;m also using the RGB color space to run the k-means clustering algorithm to extract an eight-color palette. In addition to showing the palette on the bottom of the image, I mapped the palette&amp;rsquo;s colors as circles in the RGB space as well. It does OK, but really misses that slate blue altogether, averaging it in with the brownish grays.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s look at a painting by Jeremy with a very different palette.&lt;/p&gt;&#xA;&lt;img alt=&#34;a green hued landscape painting&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/jeremy.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here we have lots of greens/yellow-greens, some grays and very light blue and a touch of red.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/jeremy_rgb.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Again we have that sweep from black to white. We pick up a lot of green variations, but just barely catch a hint of blue, and miss the reds altogether, even with an expanded palette of ten colors.&lt;/p&gt;&#xA;&lt;p&gt;The next example is one I grabbed with &lt;code&gt;getimage&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;photo of some colored eggs in stands&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/eggs.png&#34; /&gt;&#xA;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/a-multi-color-eggs-7987035/&#34;&gt;Anuja Tilj on Pexels&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This has strong areas of green, dark reddish-purple, orange and yellow, with a lot of off-white/tan. How does this look in RGB space?&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/eggs_rgb.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Pretty big fail here. It focuses on finding a whole bunch of shades of brown and tan. The only bright color it really finds is a bit of that yellow. One of the problems here is the way that similar colors sweep along in an arc from dark to light. Because of this, the colors on one end of the arc are quite distant from colors on the other end, and will be averaged in with other closer colors. This is similar to the &amp;ldquo;two moons&amp;rdquo; data set, which is a known problem for k-means.&lt;/p&gt;&#xA;&lt;img alt=&#34;two sets of curved data sets, colored red and blue&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/two_moons.png&#34; /&gt;&#xA;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://uva-applied-ml.readthedocs.io/en/latest/notebooks/6_kmeans_clust.html&#34;&gt;https://uva-applied-ml.readthedocs.io/en/latest/notebooks/6_kmeans_clust.html&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;You&amp;rsquo;d expect each arc to be a related cluster, but k-means works strictly on distance. There are other clustering algorithms that deal with this kind of problem better. But we still have something up our sleeves here.&lt;/p&gt;&#xA;&lt;p&gt;But before we move on, let&amp;rsquo;s just see a random distribution of points in RGB space. Here I just filled every point in the image with a random RGB color.&lt;/p&gt;&#xA;&lt;img alt=&#34;a noisy image where every pixel is random&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/randomrgb.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And the visualization&amp;hellip;&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/randomrgb.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Because everything is very smoothly distributed here, with just about every color represented, the algorithm does a pretty good job at picking out all the different colors and shades.&lt;/p&gt;&#xA;&lt;p&gt;But what do we do about the failures in the more realistic images? Well, I keep saying that RGB isn&amp;rsquo;t a great space for this. Let&amp;rsquo;s try another space.&lt;/p&gt;&#xA;&lt;h2 id=&#34;visualizing-in-hsv&#34;&gt;Visualizing in HSV&lt;/h2&gt;&#xA;&lt;p&gt;HSV is another way of describing colors. Think of the space as a cylinder. Going around the outside perimeter, from 0 to 360 degrees we have different hues, starting at red, going through orange and yellow, green, cyan, blue, violet and back to red at 360.&lt;/p&gt;&#xA;&lt;p&gt;Saturation is how much of the color is there. A saturation of 0 gives you grayscale, where a full saturation of 1.0 (or 100%) gives you the brightest shade of the color possible.&lt;/p&gt;&#xA;&lt;p&gt;And value is how dark it is. Value 0 will be black and value 1.0 (or 100%) will be bright.&lt;/p&gt;&#xA;&lt;p&gt;In the HSV space, we can think of saturation as going from the center of the cylinder as saturation 0, to the outside edge with saturation 1. And value will be 0 at the bottom of the wheel and 1 at the top.&lt;/p&gt;&#xA;&lt;img alt=&#34;the hsv color model&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/hsv.png&#34; /&gt;&#xA;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://commons.wikimedia.org/w/index.php?curid=9801673&#34;&gt;By HSV_color_solid_cylinder.png: SharkDderivative work: SharkD  Talk - HSV_color_solid_cylinder.png, CC BY-SA 3.0,&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Here&amp;rsquo;s a bunch of random color points in the HSV color space.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/hsv_color_space.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;You can see that colors near the center are grayscale, and the colors out at the edge and top are the brightest and most vibrant. The benefit of this color space over RGB is that similar colors are more likely to be close together. Let&amp;rsquo;s see how it looks.&lt;/p&gt;&#xA;&lt;p&gt;First with Todd&amp;rsquo;s painting&amp;hellip;&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/todd_hsv.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This one kind of blew my mind. The pixel cloud forms a nearly flat plane slicing right through the middle of the HSV cylinder. To me this shows a nicely executed complementary color scheme, with the slate blue directly across from the reddish-oranges, but quite understated, just enough for contrast. It also shows a good range of values from dark to light.&lt;/p&gt;&#xA;&lt;p&gt;In addition to visualizing the pixels in HSV space, I also changed the palette extraction code (the k-means clustering) to use the HSV space to find the clusters.&lt;/p&gt;&#xA;&lt;p&gt;The new algorithm did a somewhat better job of getting to that slate blue. But I think the visualization shows that, numerically, there is not as much blue there as the eye sees in the painting. The contrast with the brown/orange makes the blue tint really stand out. But seen in the visualization, there&amp;rsquo;s really not a whole lot of blue there, and it&amp;rsquo;s not very saturated.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s see Jeremy&amp;rsquo;s now&amp;hellip;&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/jeremy_hsv.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Much better performance here. We got the blue, and some of the dark red this time. I&amp;rsquo;m happy with this.&lt;/p&gt;&#xA;&lt;p&gt;Also, it&amp;rsquo;s purely coincidental, but I find it interesting that the visualization almost looks like the scene it is representing. A bit more on that concept later!&lt;/p&gt;&#xA;&lt;p&gt;And finally the eggs&amp;hellip;&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/eggs_hsv.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;A big win here! We got the red/purple, green, orange, and yellow, as well as a range of browns/tans.&lt;/p&gt;&#xA;&lt;p&gt;All in all, I was very excited about the visualizer itself, and it helped me to see what was going on and improve the palette extraction method from RGB to HSV, which itself is now improved. As I said in the last post, this is never going to be &amp;ldquo;perfect&amp;rdquo;, but it&amp;rsquo;s better now than it was a week ago. I do still need to update the &lt;a href=&#34;https://bit-101.com/blog/posts/2026-01-11/getpal/&#34;&gt;getpal&lt;/a&gt; tool to use the HSV method. I&amp;rsquo;ll do that soon, but wanted to get this post out.&lt;/p&gt;&#xA;&lt;h2 id=&#34;bonus-fancy-trick&#34;&gt;Bonus Fancy Trick!&lt;/h2&gt;&#xA;&lt;p&gt;As I was working through the first versions of this tool, I posted some of the videos on Mastodon. &lt;a href=&#34;https://mstdn.social/@junklight@mastodon.art&#34;&gt;Matilda&lt;/a&gt; responded with this post:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/mastodon.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;This concept hit me like a bolt of lightning. Within a fraction of a second I knew exactly how I would do this, and had already started writing the code in my head. Sadly, I was away at a company off site and didn&amp;rsquo;t have time to get to it for a while. I also wanted to clean up all the visualization and HSV palette code before embarking on a new project. But I finally got around to it a few days later. Here&amp;rsquo;s the walk-through.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Get a 3D point cloud model.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I was all set for this. In 2024 I created &lt;a href=&#34;https://codeberg.org/bit101/wire&#34;&gt;a library I called Wire&lt;/a&gt;. This did 3D wire-frame rendering and could load and render point clouds in &lt;code&gt;xyz&lt;/code&gt; format. While working on that, I&amp;rsquo;d gathered a few point cloud models. One I used often was a human skull. It was composed of 593,934 3D points. The &lt;code&gt;xyz&lt;/code&gt; format is very simple. Here&amp;rsquo;s a sample from the &lt;code&gt;skull.xyz&lt;/code&gt; file I have:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;443.25656128 439.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;442.25656128 448.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;439.25656128 459.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;386.25656128 460.95645142 1716.12145996&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;438.25656128 461.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;420.39944458 463.95645142 1716.12145996&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;437.25656128 464.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;436.25656128 466.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;374.39944458 469.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;435.25656128 469.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;375.39944458 471.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;434.25656128 471.81356812 1715.9786377&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s just one line per point, x, y and z coordinates separated by spaces.&lt;/p&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;Parse the point cloud into color values.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;There are a couple of problems right off the bat. First there are way too many points in this model. Second, the values are not in a usable range. So I wrote a short program to open the file, read every 50th line and map the coordinates to be between -0.5 and 0.5 on each axis. Then, considering those were HSV color space coordinates, I was able to back port them to hue, saturation and value. Then convert that HSV color to an RGB color and save them all to a file. Here&amp;rsquo;s a small portion of that file, &lt;code&gt;skull.pal&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#555&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;67d551&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;64d2a0&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f1acb1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;8ad149&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f3a2f2&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;5ad75b&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f4a2f1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;68d552&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;b3c83d&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;70d45b&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;67ceca&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;bbc637&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;7dd34a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f3a8c9&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;afc941&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;cbc149&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;98ce4d&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;bdc63d&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;6bd550&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;deb97c&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;8cc5eb&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;8bd152&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;60d575&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;aacb46&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e8b2a2&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;beb7f4&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e7b2a1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;dfb87e&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;eba6f7&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;dfb97a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;bfc639&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;61d3a1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;76d458&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;61d57a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;97cf42&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;90d143&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;76d45b&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;79d44e&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e2b781&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;60d580&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;b4bafa&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;67d39a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;77d459&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;a1cd52&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e7b39d&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;c4c53a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e8b39f&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f6a2f2&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;5ad858&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f3a4f0&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;c3b6fa&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;96cf54&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;61d76c&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;72d557&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;ecb29a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e7b49a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;8dd243&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;a0ce40&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;67cfd2&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e8b49b&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;d1c152&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;a4cd53&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f4a9cf&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;cdb3fc&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;aacc49&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;a6cd48&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;dabd65&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;70d568&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f2a8de&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;68d764&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e7b596&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;b8bbf6&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;77d551&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;debb75&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;64d85a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;76d55f&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e7b692&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;b9bbf9&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;bfb9f4&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;eeafbf&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;ecb0bc&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;84d44a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;cec256&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;8bc9e2&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;f2a6f7&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;6cd76e&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e0adfa&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;6ad58b&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;c2c741&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;9ec3f0&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;cbc44f&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;d8bf6a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;7eccdf&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;7ad566&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;5ed965&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;6ad4a4&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;64d872&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;85d549&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;95d24f&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;67d4aa&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;cfb5f4&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;64d4b0&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;61d2cf&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;66d0d9&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;7bcce4&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;c5b9f3&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;edb1bf&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;96d251&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;9bc5f1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;76d762&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;62d4b6&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;9dd154&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;67d78d&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;5dd3cb&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;aace4c&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;cab7fc&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;7ccde0&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;a0d145&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;83d55c&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;70d865&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;61d4bc&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;eab790&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;eab6a2&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;83d560&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;e2bb88&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;75cfd8&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;#&lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;70d86a&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a list of with 11,645 color values.&lt;/p&gt;&#xA;&lt;ol start=&#34;3&#34;&gt;&#xA;&lt;li&gt;Generate an image with those colors.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Now those colors were generated from 3D points in the HSV color space, so if I plotted them all in the visualization tool, they would convert back into the original x, y, z points and form a 3D image of a skull!&lt;/p&gt;&#xA;&lt;p&gt;So I created a new image and for every pixel in that image I randomly chose one of those 11,645 values to color that pixel with. This resulted in an image like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/pal_sculpt_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Not very interesting though. So I sorted all the pixels by hue, saturation and value and got this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/pal_sculpt_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Better, but still a bit bland. What if I warped it a bit with some Simplex noise? In this case I let the noise wrap, so that if a pixel is pushed off one edge, it returns to the opposite side of the image. Now we have:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/pal_sculpt_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now that&amp;rsquo;s nice and funky and should still have roughly the same random pixels from the skull palette. Let&amp;rsquo;s feed it into the visualizer and&amp;hellip;&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/skull.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;How crazy is that?&lt;/p&gt;&#xA;&lt;p&gt;For one more test, I generated 10,000 3D points using a bit of math to make a 3D shape. I converted those points to a palette, sorted and warped it just like the point cloud skull. It looks like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-18/spiral_pal.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now, if you look at that, it looks pretty damn similar to the image generated with the skull palette. But the colors are slightly different. When you run this through the visualizer, you get:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2026-01-18/spiral.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I could go on, and may post some more examples later, but you get the point.&lt;/p&gt;&#xA;&lt;p&gt;Is this important or useful? Probably not. But I think it&amp;rsquo;s cool as hell and I&amp;rsquo;ll always play with stuff like this.&lt;/p&gt;&#xA;&lt;h2 id=&#34;thanks&#34;&gt;Thanks&lt;/h2&gt;&#xA;&lt;p&gt;Shout out for Jeremy and Todd for being nice enough to let me mess around with their paintings. And for Matilda for the spectacular idea. All three are worthy follows on Mastodon.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Getpal - extract a color palette from an image</title>
      <link>https://bit-101.com/blog/posts/2026-01-11/getpal/</link>
      <pubDate>Sun, 11 Jan 2026 09:16:29 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-01-11/getpal/</guid>
      <description>&lt;p&gt;At the end of my &lt;a href=&#34;https://bit-101.com/blog/posts/2026-01-04/palettes/&#34;&gt;post about color palettes&lt;/a&gt; I talked about extracting palettes from images. I said there that I&amp;rsquo;d gone through a few different strategies but wasn&amp;rsquo;t really satisfied with any of them. And I said that I wanted to try &lt;a href=&#34;https://en.wikipedia.org/wiki/K-means_clustering&#34;&gt;k-means clustering&lt;/a&gt; to see if that would have better results. I implemented k-means a few months back on 2D points and got it working pretty well, but for some reason I thought that doing it with colors would be a lot more complicated. Turns out, it was pretty easy to adapt the 2D clustering code to 3D - where the 3 dimensions were red, green and blue.&lt;/p&gt;&#xA;&lt;h2 id=&#34;light-intro-to-k-means&#34;&gt;Light intro to k-means&lt;/h2&gt;&#xA;&lt;p&gt;The k-means algorithm helps you find &amp;ldquo;clusters&amp;rdquo; in data sets. For example, say you have a set of coordinates where crimes have happened over a city or state. You can plot them on a map and maybe get a visual idea of where the worst crime locations are. There might be three or four hot spots. k-means is just an algorithmic way of doing that.&lt;/p&gt;&#xA;&lt;p&gt;Say you have the following points mapped out.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You can visually see that there are clumps or clusters where there are more points, but how would you go about identifying those? If you&amp;rsquo;re not familiar with k-means, think about how you might do that for a bit.&lt;/p&gt;&#xA;&lt;p&gt;k-means gives us the following 16 clusters, each color coded:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here, I&amp;rsquo;ve drawn a circle at the center of each cluster. Each point is assigned to the cluster where it is closest to the center. That&amp;rsquo;s where the name comes from. Each cluster is formed by an average or &amp;ldquo;mean&amp;rdquo;, and there are a specific number of them - k. So you have k means.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now, you might not agree with some of those clusters. For example, that yellow cluster on the right edge, you might be thinking that should be three separate clusters. The thing is, k-means requires you to set a value for how many clusters you want to sort your data into. I chose 16 and it did its best with that. If I specify 28 clusters, we get this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now, it got three different clusters for those ones on the right, but maybe there are others where you feel that two or more clusters could be joined. The takeaway is that the algorithm is very dependent on the initial number of clusters you specify and you may not always get the results you expect.&lt;/p&gt;&#xA;&lt;p&gt;There are more advanced features that people have added to the algorithm over the years, but this is just the original basic algorithm.&lt;/p&gt;&#xA;&lt;h3 id=&#34;how-it-works&#34;&gt;How it works&lt;/h3&gt;&#xA;&lt;p&gt;To start with, you create k means, i.e. you create a specific number of &amp;ldquo;centroids&amp;rdquo; (in this case 2D points), which will eventually become the centers of each cluster. So again, you have to choose how many means you want. For this discussion, let&amp;rsquo;s say we choose five. So we create five points to serve as centroids.&lt;/p&gt;&#xA;&lt;p&gt;Then, we have to decide how to create these centroids. Where do we put them? Well, there are various strategies for that. The simplest is to just create random points that will fit within the range of the data. That generally works surprisingly well, though there can be some differences in the outcomes on different runs. It can also have some other problems we&amp;rsquo;ll explore later. But often it&amp;rsquo;s a good enough strategy. Other strategies involve some randomness but making sure that your centroids are spread around the data set and not randomly clumped together.&lt;/p&gt;&#xA;&lt;p&gt;Now that you have your five centroids, you loop through all the data points and assign each one to one of five clusters based on the five centroids. You find out which centroid each point is closest to and assign it to the cluster associated with that centroid.&lt;/p&gt;&#xA;&lt;p&gt;Now you have five clusters, but they are probably not optimal, especially if you chose random centroids. So, you go through each cluster and find the mean location of each one of them - the average location of all the points. In most cases, this new mean will be different than the existing centroid. So you take those five new means and make them the new centroids for five new clusters. And you loop through the points again and assign them. You&amp;rsquo;ll likely have different assignments.&lt;/p&gt;&#xA;&lt;p&gt;And you repeat that over and over - assign the points, get the new means, assign the points, etc. - until the new means are the same as the previous means. This means things have stabilized and you now have your final clusters.&lt;/p&gt;&#xA;&lt;h3 id=&#34;applying-this-to-color&#34;&gt;Applying this to color&lt;/h3&gt;&#xA;&lt;p&gt;As I said, this was much simpler than I expected.&lt;/p&gt;&#xA;&lt;p&gt;First I gather all the pixels in the image. Each one has red, green and blue values from 0.0 to 1.0. Maybe you want to use 0 to 255. But these three values let you think of a color as a point in 3D space.&lt;/p&gt;&#xA;&lt;p&gt;Then you decide how many colors you want in your palette. Again, lets go with five. You create five centroids, which themselves are color values.&lt;/p&gt;&#xA;&lt;p&gt;Now you loop through all your pixels and find out which centroid they are closest to. This again is Euclidian difference. You take each pixel and get its difference to the centroid on the red, green and blue &amp;ldquo;axes&amp;rdquo;, call them &lt;code&gt;dr&lt;/code&gt;, &lt;code&gt;dg&lt;/code&gt;, &lt;code&gt;db&lt;/code&gt;. Then it&amp;rsquo;s just &lt;code&gt;sqrt(dr * dr + dg * dg + db * db)&lt;/code&gt;. (See note on this later.)&lt;/p&gt;&#xA;&lt;p&gt;Then you get the average color of all the pixels in each cluster. Again, this is adding up all their reds, greens and blues and dividing each by the number of pixels in that cluster. Those are your new means and you repeat until the means don&amp;rsquo;t change.&lt;/p&gt;&#xA;&lt;p&gt;Now, each of those means/centroids are an average of the pixels in that cluster, meaning that list of centroids is your sampled palette!&lt;/p&gt;&#xA;&lt;p&gt;Say we start with this image:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/src_image.png&#34; /&gt;&#xA;&#xA;Photo by Ella Olsson: &lt;a href=&#34;https://www.pexels.com/photo/top-view-of-food-1640772/&#34;&gt;https://www.pexels.com/photo/top-view-of-food-1640772/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Running through this algorithm, we get the palette shown on the bottom of the image:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I think this has done a pretty good job. It&amp;rsquo;s got the dark brown, then the mid brown with a hint of green, the reddish orange, a lighter brown and an off-white. It didn&amp;rsquo;t really grab the green in this case, but if we up the palette to a count of six, we get it.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Interestingly, that second mid-range brown now seems to have less of a green tint. The green pixels got pulled into their own cluster, leaving that one just plain brown.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-tool-and-all-the-pieces&#34;&gt;The tool and all the pieces&lt;/h2&gt;&#xA;&lt;p&gt;I built a standalone tool that you can run as a command line app. You can find it here: &lt;a href=&#34;https://codeberg.org/bit101/getpal&#34;&gt;https://codeberg.org/bit101/getpal&lt;/a&gt;. There are binaries for MacOS and Linux, though I think you&amp;rsquo;ll need to install the cairographics library for your platform as well.&lt;/p&gt;&#xA;&lt;p&gt;But before I built the tool, I built all the code to let me do this within anything I happen to be coding at the time. Here are the pieces that go into it:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;A function to get a list of all the pixels in an image. I can do that by loading an external image or grabbing the pixels from the drawing context of an image I am working on. This just returns an array with an RGB value for each pixel. For an 800x800 picture, this is an array with 160,000 elements. For a much larger image, this could get out of hand. So the function lets me set a sampling resolution. If I set that to 4, it will sample every fourth row and column, in other words, one pixel for every sixteen. This speeds things up a lot but still usually gives you a good representation of the color values. The code for all of this is here: &lt;a href=&#34;https://codeberg.org/bit101/blcairo/src/branch/main/context_histogram.go&#34;&gt;https://codeberg.org/bit101/blcairo/src/branch/main/context_histogram.go&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The k-means function itself. This takes the array of pixels and a &lt;code&gt;k&lt;/code&gt; value - how many colors you want in the palette. This returns the palette as an array of color values, to use as you will. If you want to gaze at the code, it&amp;rsquo;s here: &lt;a href=&#34;https://codeberg.org/bit101/bitlib/src/branch/main/blcolor/kmeans.go&#34;&gt;https://codeberg.org/bit101/bitlib/src/branch/main/blcolor/kmeans.go&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;There are some nuances though&amp;hellip;&lt;/p&gt;&#xA;&lt;h3 id=&#34;choosing-the-initial-centroids&#34;&gt;Choosing the initial centroids&lt;/h3&gt;&#xA;&lt;p&gt;Initially I was, as described earlier, just creating random color values for the initial centroids. This had mixed (and not very stable) results. You could get different palettes each time you ran the tool. Usually pretty close, but there would be differences. Sometimes things would be way off. I ran across an example that showed very clearly how random centroid initialization can get you into trouble, and reproduced it here.&lt;/p&gt;&#xA;&lt;p&gt;Say you have a random set of values that are naturally clustered together like so:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And say you wanted to extract two clusters from this. You would expect the algorithm to group them as so:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;But say your initial two centroids were placed near the center like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_09.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Then k-means would return the clusters defined like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_10.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;That looks ridiculous, but all the points in the top cluster are closest to the top centroid and all the ones in the bottom are closest to that centroid, so it&amp;rsquo;s stable and correct! There are other configurations which would cause other similar problems.&lt;/p&gt;&#xA;&lt;p&gt;So random centroid initialization can give you random problems. And I would see some odd results now and then when using random RGB initial centroids. In fact, it&amp;rsquo;s surprising how well it worked most of the time with random initialization. But I wanted to see if I could do better.&lt;/p&gt;&#xA;&lt;p&gt;Ideally you&amp;rsquo;d want your initial centroids spread throughout the data. Of course, they&amp;rsquo;ll change and move around as the algorithm iterates, but you tend to get better results if they are spread out to start with.&lt;/p&gt;&#xA;&lt;p&gt;I came up with a seemingly well-working strategy using HSV. If I want five colors in my palette, I initialize the centroids with HSV colors where the hue ranges from 0 to 360, like 0, 72, 144, 216, 288. Since a hue of 0 is the same as 360, this ensures that each centroid&amp;rsquo;s hue is 72 degrees from any other one. Of course, this is done mathematically based on the number of palette colors specified. I used a saturation and value of 0.5 for each color as well. That just seemed to work the best.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m pretty happy with how this worked out. It seems to capture those less represented colors, like the green in the above photo, better than the random method for the most part.&lt;/p&gt;&#xA;&lt;h3 id=&#34;choosing-the-final-color-for-each-cluster&#34;&gt;Choosing the final color for each cluster&lt;/h3&gt;&#xA;&lt;p&gt;You can use the color value of the centroid as final palette color for that cluster, which is what I did at first. It generally works OK, but realize that is not necessarily a color that is actually in the original image. It&amp;rsquo;s an average of all the colors in that cluster. If you have a bunch of colors &lt;em&gt;around&lt;/em&gt; the centroid, but none exactly near it, you could wind up with a color that doesn&amp;rsquo;t seem to fit at all. So I gave myself three options here:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Take the average as described above. This is deterministic, meaning you should get the same color each time.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Take the pixel from that cluster that is closest to the centroid. This is guaranteed to be an actual color that exists in the image, and again is deterministic.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Take a random pixel from that cluster. This is guaranteed to be a color in the image, but is non-deterministic.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;the-tool-itself&#34;&gt;The tool itself&lt;/h3&gt;&#xA;&lt;p&gt;When you run the tool, you pass it the path to an image it will spit out a comma-separated list of color values. You can also specify the number of colors in the palette with the &lt;code&gt;-n&lt;/code&gt; flag. This defaults to 5.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_11.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You can use the &lt;code&gt;-s&lt;/code&gt; or &lt;code&gt;--swatch&lt;/code&gt; flag passing it the name of a new png to create, which will visually show you the palette it extracted. Running &lt;code&gt;getpal -n 8 -s palette.png&lt;/code&gt; for example, generates this image:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_12.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;By default, it will sample at a resolution of 4 as described above. You can change this with the &lt;code&gt;-r&lt;/code&gt; flag. But pay attention to the fact that setting this to 10 for example would not sample every tenth pixel, but every tenth row and column, so every one-hundredth pixel, which is starting to get into the realm of missing some color values, for a smaller image at least.&lt;/p&gt;&#xA;&lt;p&gt;Finally, the &lt;code&gt;-m&lt;/code&gt; or &lt;code&gt;-method&lt;/code&gt; flag lets you specify the method of choosing the final color for each cluster as just described, the values are &amp;ldquo;average&amp;rdquo;, &amp;ldquo;closest&amp;rdquo; and &amp;ldquo;random&amp;rdquo;, with &amp;ldquo;average&amp;rdquo; being the default.&lt;/p&gt;&#xA;&lt;h2 id=&#34;caveats-and-tips&#34;&gt;Caveats and tips&lt;/h2&gt;&#xA;&lt;p&gt;As analytical as all of this is, it&amp;rsquo;s not hard science. Some things to realize:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;As seen in the photo example above, and as described earlier, the number of colors you specify can have a very big effect on the palette you get. If you&amp;rsquo;re hoping to get a particular color in the palette, you might have to specify a larger palette and  manually cull some of the colors you don&amp;rsquo;t want.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The method argument can play a big part in fine tuning the palette you want.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;RGB is useful way of mathematically specifying color values, but it does not map perfectly onto human color perception. We see some colors as brighter or dimmer than others even when they have the same value. Some colors stand out to us more than others. The contrast between neighboring colors can change our perception of a given area of color. Like in one of my favorite optical illusions.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-11/image_13.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The brown square in the center of the top face of the cube and the yellowish square in the center of its left side are actually the same color! You might be frustrated trying to find that yellow in the palette, and only seeing the brown emerge.&lt;/p&gt;&#xA;&lt;p&gt;There are other color spaces that attempt to adjust for these perceptual differences. To keep things simple, I did not implement any of those. But to help a little bit, I use a distance function that calculates the &lt;em&gt;perceptual&lt;/em&gt; distance between two colors, rather than the raw RGB distance. I don&amp;rsquo;t know how much it actually helps in the long run, but I left it in there.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;In no way is this meant to be a definitive &amp;ldquo;these are the exact five colors that most represent this image&amp;rdquo; type of tool. I don&amp;rsquo;t even know if such a thing is possible. But it does pretty damn well, in my estimation, on grabbing a number of colors out of the image that seem to fit. Far, far better than all the other methods I&amp;rsquo;ve tried. Use it more as an inspirational tool, grab more colors than you need and choose the ones you like the best, play around with it.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Palettes</title>
      <link>https://bit-101.com/blog/posts/2026-01-04/palettes/</link>
      <pubDate>Sun, 04 Jan 2026 12:35:51 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-01-04/palettes/</guid>
      <description>&lt;h2 id=&#34;color-is-hard&#34;&gt;Color is hard&lt;/h2&gt;&#xA;&lt;p&gt;Most of what I do is in black and white. I love the way it looks. I like building up forms and shapes and patterns using only values.&lt;/p&gt;&#xA;&lt;p&gt;Also&amp;hellip; color is hard.&lt;/p&gt;&#xA;&lt;p&gt;Or, it seems hard. Working with color reminds me of composing music. There are so many rules, and it&amp;rsquo;s all based on physics, and also all based on human perception, and also psychology - though that last one is a bit questionable.&lt;/p&gt;&#xA;&lt;p&gt;But I&amp;rsquo;m trying to learn new things, so I did a deep dive into colors and palettes recently.&lt;/p&gt;&#xA;&lt;p&gt;My &lt;a href=&#34;https://codeberg.org/bit101/bitlib&#34;&gt;bitlib&lt;/a&gt; coding library already had a &lt;a href=&#34;https://codeberg.org/bit101/bitlib/src/branch/main/blcolor&#34;&gt;pretty rich set of color functions&lt;/a&gt;. I&amp;rsquo;ve coded up color creation in RGB with floats (0-1) or ints (0-255), HSL, HSV, CMYK, all named CSS colors, functions for working with brightness and contrast and mapping new colors to an existing image.&lt;/p&gt;&#xA;&lt;p&gt;But whenever I did use color I&amp;rsquo;d most often just use my &lt;code&gt;RandomRGB&lt;/code&gt; function to generate random colors. It mostly ended up looking like crap.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Occasionally I&amp;rsquo;ll spread things out across a spectrum, like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Or, if I&amp;rsquo;m feeling really adventurous, I&amp;rsquo;ll use HSV with a single hue and random saturations and hues for a monochrome composition&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s kind of cool, but I&amp;rsquo;ve always felt like I&amp;rsquo;m missing something.&lt;/p&gt;&#xA;&lt;p&gt;I even wrote a color palette module a while back. But never did anything with it. Mostly because it had such limited functionality. You could create a palette and then manually add colors to it. Then&amp;hellip; use those colors &amp;hellip; somehow, later. Basically it was just an array of color values. The only real useful thing I did with it was let you map a palette to an image. So all the dark colors would map to the first few colors in the palette, and the lighter ones to colors at the end of the list. This was pretty fun because I could make a grayscale image and then map any palette to it.&lt;/p&gt;&#xA;&lt;p&gt;Like, I could take this picture of my dog&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_03a.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Blur it, grayscale it, and map a monochrome palette to it and get this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_03b.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;But creating a palette was a pain. So I still never used it. So I decided to do some work on it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;creating-a-useful-palette-module&#34;&gt;Creating a useful palette module&lt;/h2&gt;&#xA;&lt;p&gt;I started doing some research on color palettes, or color themes or color schemes. All basically the same thing - a limited set of color values that &lt;em&gt;supposedly&lt;/em&gt; work well together. A lot of this I already knew, but for the first time tried to implement some of it in code.&lt;/p&gt;&#xA;&lt;p&gt;So over the course of a day or two I sat down and wrote a bunch of palette creation methods.&lt;/p&gt;&#xA;&lt;h3 id=&#34;monochrome&#34;&gt;Monochrome&lt;/h3&gt;&#xA;&lt;p&gt;The first one I did was the monochrome palette. This was similar to what I did above. But in the above I was choosing a new random color for each triangle I drew. The interesting thing about a palette is that it&amp;rsquo;s usually more limited. A palette of two colors is totally valid. Often they only contain a handful of colors. A couple dozen might be a lot, but it depends on what you&amp;rsquo;re doing I guess.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, my method takes a hue and the number of colors you want in the palette. Rather than randomly creating color values, it spreads them out across a range of saturation and values. In pseudocode&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; MonoPalette(hue, n) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;pal &lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt; NewPalette()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; n; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;sat &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; (n &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;val &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; s&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// avoid 0 saturation or luminance, which is just grayscale&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;sat &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(sat, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0.4&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;val &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(val, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0.2&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;pal.Add(HSV(hue, sat, val))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; pal&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I create a new palette and loop through n times.&lt;/p&gt;&#xA;&lt;p&gt;Saturation is &lt;code&gt;i / (n - 1)&lt;/code&gt; which gets it to range fully from 0 to 1, and value will spread oppositely from 1 to 0. But when I had that, I found that the zero saturations were just white, and the zero values were just black. So I used a &lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-26/fotd-map/&#34;&gt;map&lt;/a&gt; function to map those values to keep them away from zero. The values I ended up with were through trial and error and what I thought looked good. Most of the other palette methods I made were pretty similar to this: create a palette, calculate a number of colors, add them to the palette, return the palette.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a monochrome palette with six colors.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Although this uses the same hue as the version above, it feels more monochrome-y to me. But that&amp;rsquo;s what&amp;rsquo;s great about coding your own library. You can do whatever feels right.&lt;/p&gt;&#xA;&lt;h3 id=&#34;achromatic&#34;&gt;Achromatic&lt;/h3&gt;&#xA;&lt;p&gt;After that it was easy to create an achromatic palette. In other words, grayscale. In the past I would have just chosen random values of gray for each triangle, like so:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And this looks fine. But here, I just use a 5-color achromatic palette:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I&amp;rsquo;m not claiming it looks &lt;em&gt;better&lt;/em&gt;, but it&amp;rsquo;s different. Fewer shades, more contrast, cripser. And now I have control. I can make a straight up black and white palette, or one with as many shades as I need for something to look the way I want.&lt;/p&gt;&#xA;&lt;p&gt;I won&amp;rsquo;t show pseudocode for all these examples (the actual Go code is linked above). But this was pretty similar to the technique of the monochrome. Iterate n times across gray scale values from 0 to 1 and add each one of those colors to the palette.&lt;/p&gt;&#xA;&lt;h2 id=&#34;color-theory-cognitive-dissonance&#34;&gt;Color Theory Cognitive Dissonance&lt;/h2&gt;&#xA;&lt;p&gt;Next I went for the common color-wheel-based palettes you read about in color theory. I might have a bit of a rant here, so be forewarned.&lt;/p&gt;&#xA;&lt;p&gt;These palettes include complementary, split-complementary, triadic, tetradic, and a few others. They are defined by the angles between the various colors within the palette, as seen on the color wheel.&lt;/p&gt;&#xA;&lt;p&gt;Complementary colors are those right across from each other on the wheel, or 180 degrees.&lt;/p&gt;&#xA;&lt;p&gt;Split-complementary means you go across, and then choose the two colors 30 degrees to either side of that. So 150 and 210 degrees.&lt;/p&gt;&#xA;&lt;p&gt;In in a triadic palette, you choose three colors that are 120 degrees from each other - they form an equilateral triangle. And tetradic contains four colors 90 degrees apart, forming a square.&lt;/p&gt;&#xA;&lt;p&gt;Now in any text about color theory, you&amp;rsquo;ll read all about why these particular arrangements are so important. About harmony and tension, contrast and resolution. Some might go into wavelengths, others into psychology, how the various colors, by being in those exact relationships and angles with each other set up particular feelings and emotions in the brain.&lt;/p&gt;&#xA;&lt;p&gt;But here&amp;rsquo;s the thing. Most of those texts are referring to the RYB color wheel. That&amp;rsquo;s the one you learned in elementary school. Red, yellow, blue as primary colors. Mix them to get orange, green, and purple, the secondary colors. Mix again to get the tertiary colors, which I won&amp;rsquo;t show here.&lt;/p&gt;&#xA;&lt;p&gt;Each of these colors is 60 degrees apart from its neighbor on the wheel.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;So in this color wheel, red and green would be complementary. As would yellow and purple, and blue and orange.&lt;/p&gt;&#xA;&lt;p&gt;Triadic colors would be red, yellow, blue, or purple orange green. Each one of the colors in each triad is 120 degrees from its neighbors.&lt;/p&gt;&#xA;&lt;p&gt;Of course there are a huge number of other triadic and complementary palettes. But since my wheel only shows six colors, that&amp;rsquo;s what&amp;rsquo;s I&amp;rsquo;ll mention.&lt;/p&gt;&#xA;&lt;p&gt;You&amp;rsquo;d need to have the tertiary colors to get split complementary and tetradic.&lt;/p&gt;&#xA;&lt;p&gt;All good so far.&lt;/p&gt;&#xA;&lt;p&gt;But then we get computers. And RGB and HSL/HSV. And we get the RGB color wheel.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;With this wheel, the primary colors are red, green, blue, and secondary are cyan, yellow and magenta. These are also the two triadic palettes you can get with just these six colors. Complementary colors are red and cyan, yellow and blue, magenta and green.&lt;/p&gt;&#xA;&lt;p&gt;Now my problem is not that they are different. That&amp;rsquo;s fine. Different models, different paradigms, different technologies, additive vs subtractive, etc. I get it. All good.&lt;/p&gt;&#xA;&lt;p&gt;My problem is that artists have gone on for years about, for example, the importance of using complementary colors and how important they are and the effects they have on the viewer. And they&amp;rsquo;re talking about, say, red and green. And now we come along with our RGB and people are still talking about complementary palettes and how these two very specific colors are going to affect your brain. But they&amp;rsquo;re talking about red and cyan instead of red and green.&lt;/p&gt;&#xA;&lt;p&gt;I actually saw a site that went over all of this, complementary, triadic, tetradic, split, analogous and all the reasons to use each one, showing them on a static RYB color wheel. Then said, click here for an interactive color wheel you can explore these on. And it went to an RGB color wheel!&lt;/p&gt;&#xA;&lt;p&gt;In short, I agree with the general ideas of these color palettes - it can be good to have two very different colors, or two similar colors and one different, or three or four fairly different colors. But to say that these exact angles on the color wheel magically ignite our psyches into feeling a certain way is a bit of a stretch, especially when those angles give you totally different colors depending on which wheel you are using.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, end of rant. On with the show!&lt;/p&gt;&#xA;&lt;h2 id=&#34;more-color-palettes&#34;&gt;More color palettes!&lt;/h2&gt;&#xA;&lt;p&gt;For a bunch of these, HSV or HSL is going to be your friend. Especially if the hue argument is set up to take values from 0 to 360. And even better if these will wrap around, so 380 is really 20, and -30 is the same as 330.&lt;/p&gt;&#xA;&lt;p&gt;For complementary, you take one color, convert it to HSV, add 180 to the H and create your second color.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_09.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;For split-complementary, you take the original color, H + 150, and H + 210.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_10.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;For triadic it&amp;rsquo;s original, H + 120, H + 240.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_11.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And tetradic is original, H + 90, H + 180, H + 270.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_12.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You can see these all start out with the same orangey color and have other colors added to them. In these cases, I&amp;rsquo;m just choosing a random palette entry for each triangle, so it gets a bit chaotic. Often the advice is to choose one or two colors as the main colors and use the others sparingly for contrast. So you might get things like&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_13.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_14.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_15.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_16.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;In each case, I left the orange color as the contrast. The tetradic was the tougher one because all the colors differ significantly. But these are contrived examples anyway. In a real design you might have the background one color, foreground another. Maybe some text in a very contrasting color to pull your attention to it. Whatever.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-few-more&#34;&gt;A few more!&lt;/h2&gt;&#xA;&lt;p&gt;There&amp;rsquo;s an analogous palette where you take the original, H + 30, and H - 30.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_17.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And then there&amp;rsquo;s a spectral palette, which doesn&amp;rsquo;t mean ghostly, just that it spreads across a spectrum. In this case, hues of 60 to 120, which kind of does wind up a bit ghostly.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_18.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;This is pretty similar to an analogous palette, but you can spread it out more than just 30 degrees and add as many colors as you want.&lt;/p&gt;&#xA;&lt;p&gt;Finally, I wanted to make a pastel palette. This is quite similar to the spectral one, in that it spreads across a range of two hues, in this case 0 to 60, but it give each value a randomly low saturation.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_19.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Both of these last two use the same technique as monochromatic and achromatic - interpolating between the two hues n times, and adding a color with that hue to the palette.&lt;/p&gt;&#xA;&lt;h2 id=&#34;other-palettes&#34;&gt;Other palettes&lt;/h2&gt;&#xA;&lt;p&gt;Earlier I had a bit of a rant about the over-belief in these exact angles and combinations of colors for palettes, but they&amp;rsquo;re a decent rule of thumb. However, if you go onto one of the many color palette websites out there they&amp;rsquo;ll dump thousands of potential palettes on you, with fancy names like &amp;ldquo;Olive Garden Feast&amp;rdquo; or &amp;ldquo;Pastel Dreamland Adventure&amp;rdquo; (I did not make those up!) I&amp;rsquo;m sure most of these aren&amp;rsquo;t strictly related to any of the classic palette patterns above. They&amp;rsquo;re simply combinations of colors that people thought looked good. And yeah, most of them do look pretty good. Generally there are some similar colors and one or two contrasting ones. pal&lt;/p&gt;&#xA;&lt;p&gt;So maybe I want to use one of these palettes in my library. Well often you can export a specific palette as a list of values. For example, &lt;a href=&#34;https://coolors.co&#34;&gt;https://coolors.co&lt;/a&gt; lets you copy a given palette as an array like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#cdb4db&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#ffc8dd&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#ffafcc&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#bde0fe&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#a2d2ff&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This, by the way is the infamous &amp;ldquo;Pastel Dreamland Adventure&amp;rdquo;!&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_20.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;On &lt;a href=&#34;https://colorhunt.co&#34;&gt;https://colorhunt.co&lt;/a&gt; you can also select and copy a palette pretty similarly. Other sites make it a bit more complicated but you can generally get a similar list, even if you have to copy and paste each value individually.&lt;/p&gt;&#xA;&lt;p&gt;So I made a function where you can just pass a list of strings you can just paste in and get back a palette, like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;palette &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; PaletteFromCSS(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#cdb4db&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#ffc8dd&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#ffafcc&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#bde0fe&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#a2d2ff&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And Now I can have my own pastel dreamland adventure!&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_21.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I also made functions that will save a given palette to disk in that exact format, and read a palette back in. So I have all those bases covered.&lt;/p&gt;&#xA;&lt;h2 id=&#34;palettes-from-images&#34;&gt;Palettes from images?&lt;/h2&gt;&#xA;&lt;p&gt;A oft-used creative way to come up with a palette is to sample colors from an image. So I did that. I made a function that you can pass a path to an image and a number of colors and it gives you back a palette, like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;palette &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; SampleImagePalette(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;image.png&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I grabbed this image (using my &lt;a href=&#34;https://bit-101.com/blog/posts/2025-12-31/getimage/&#34;&gt;getimage app&lt;/a&gt;!) from pexels.com&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_22.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Photo by Sergei Starostin: &lt;a href=&#34;https://www.pexels.com/photo/vibrant-tropical-poke-bowl-with-green-smoothie-34902493/&#34;&gt;https://www.pexels.com/photo/vibrant-tropical-poke-bowl-with-green-smoothie-34902493/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;And used that palette to create our usual triangle fill&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-04/image_23.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I&amp;rsquo;m not entirely satisfied with this function yet. I tried a bunch of different techniques. There are a lot of strategies to do this. Just randomly taking n number of colors from the image - but you might end up with very similar ones. I even did some work on measuring the difference between colors and making sure I didn&amp;rsquo;t have two that were too close together. Other ideas are color quantization - reducing the number of colors in the image, and creating a histogram and using colors from that. Nothing so far has really been great. So consider this a work in progress.&lt;/p&gt;&#xA;&lt;p&gt;What I want to try is &lt;a href=&#34;https://en.wikipedia.org/wiki/K-means_clustering&#34;&gt;k-means clustering&lt;/a&gt;. You&amp;rsquo;d basically plot each color in a 3D space and use the k-means algorithm to find n clusters which should give you the main colors of the image. Watch for that some time this year, if I get around to it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;Well, I think I know a bit more about color now. And I definitely have created more than enough tools for myself to explore this area. So maybe I&amp;rsquo;ll be creating a few more colorful images now&amp;hellip; maybe?&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Print Shop Is Live</title>
      <link>https://bit-101.com/blog/posts/2026-01-01/print-shop-is-live/</link>
      <pubDate>Thu, 01 Jan 2026 12:56:52 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2026-01-01/print-shop-is-live/</guid>
      <description>&lt;p&gt;Well, I&amp;rsquo;ve gone and done it. I opened a shop to sell prints of some of my digital creations. And here it is:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.etsy.com/shop/keithpetersart&#34;&gt;Keith Peters Art Shop&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.etsy.com/shop/keithpetersart&#34;&gt;&lt;img alt=&#34;&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-01/etsy.png&#34; /&gt;&#xA;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Yes, I went with Etsy to begin with. It&amp;rsquo;s at least something I&amp;rsquo;m familiar with. I&amp;rsquo;ll be looking at other sites that are more specific to selling prints.&lt;/p&gt;&#xA;&lt;p&gt;I have to say that creating the art was the easy part so far. Setting up the shop, all the financial linkages, figuring out pricing, shipping, return policies, descriptions, integration with the printer, etc. - that all was a lot of work. But now that all that is done, it should be pretty easily repeatable with future runs.&lt;/p&gt;&#xA;&lt;p&gt;Speaking of future runs, I&amp;rsquo;ve got a new line I&amp;rsquo;m working on now that I&amp;rsquo;m very excited about. Here&amp;rsquo;s a taste&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2026-01-01/001_migration.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;These began with the same technique I used in the Symmetry Web series, but I let the points wander, rather than be completely random. This creates amazing organic forms. Each one also features a ring where the colors are inverted. I look forward to sharing more of these soon.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Getimage</title>
      <link>https://bit-101.com/blog/posts/2025-12-31/getimage/</link>
      <pubDate>Wed, 31 Dec 2025 13:16:47 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-12-31/getimage/</guid>
      <description>&lt;h2 id=&#34;problem-statement&#34;&gt;Problem Statement&lt;/h2&gt;&#xA;&lt;p&gt;Sometimes when I am developing a program or technique, I need an image to load as a background or to do some image processing on, etc. It usually doesn&amp;rsquo;t matter what the image is, I just need it for testing.&lt;/p&gt;&#xA;&lt;p&gt;My usual workflow up to now:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Do an image search for something like &amp;ldquo;cats&amp;rdquo;.&lt;/li&gt;&#xA;&lt;li&gt;Find an acceptable image that is neither tiny or massive and roughly the same aspect ratio as I need.&lt;/li&gt;&#xA;&lt;li&gt;Save it, giving it a usable name like &amp;ldquo;image.png&amp;rdquo;, instead of &amp;ldquo;GettyImages-1208220393-e1612811442160.jpg&amp;rdquo;&lt;/li&gt;&#xA;&lt;li&gt;Open it up in an image editor, or use imagemagick to:&lt;/li&gt;&#xA;&lt;li&gt;Resize it to some usable dimensions. I like 800x800 as that&amp;rsquo;s the size of my canvas in my creating coding template project. But sometimes I want a different size. Scaling in imagemagick is easy, cropping is a bit more complicated, so I usually open up Gimp.&lt;/li&gt;&#xA;&lt;li&gt;If the image is not a PNG, convert it to PNG, as the graphics framework I use only loads PNGs.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;First world problems, I know. But we can do better for this kind of repetitive task.&lt;/p&gt;&#xA;&lt;h2 id=&#34;some-solutions&#34;&gt;Some solutions&amp;hellip;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;direct-random-image-download&#34;&gt;Direct Random Image Download&lt;/h3&gt;&#xA;&lt;p&gt;I looked around for random image APIs. The category of tool I wanted was something where I could hit an API endpoint and the result would be a random photo, not a JSON list of images and attributes.&lt;/p&gt;&#xA;&lt;p&gt;There are a few. Surprisingly few.&lt;/p&gt;&#xA;&lt;p&gt;The most promising was &lt;a href=&#34;https://picsum.dev/&#34;&gt;PicSum&lt;/a&gt; - does exactly what I want. Specify a width and height and it serves up a random image. And it&amp;rsquo;s free. Sadly, they are all stupid AI generated garbage. Not that it matters for my purpose, but it really annoyed me to look at those things.&lt;/p&gt;&#xA;&lt;p&gt;I then found that if you use the same API endpoints with &lt;code&gt;picsum.photo&lt;/code&gt; it will serve up an actual photo. But it does some redirects to another site that took some getting around. I got it working with a simple shell script. If you want to try that, here it is:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#!/bin/sh&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;TEMP_FILE&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;mktemp&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;[[&lt;/span&gt; -z &lt;span style=&#34;color:#033&#34;&gt;$1&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;]]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;then&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -L https://picsum.photos/800 &amp;gt; &lt;span style=&#34;color:#033&#34;&gt;$TEMP_FILE&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;elif&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;[[&lt;/span&gt; -z &lt;span style=&#34;color:#033&#34;&gt;$2&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;]]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;then&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -L https://picsum.photos/&lt;span style=&#34;color:#033&#34;&gt;$1&lt;/span&gt; &amp;gt; &lt;span style=&#34;color:#033&#34;&gt;$TEMP_FILE&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;else&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -L https://picsum.photos/&lt;span style=&#34;color:#033&#34;&gt;$1&lt;/span&gt;/&lt;span style=&#34;color:#033&#34;&gt;$2&lt;/span&gt; &amp;gt; &lt;span style=&#34;color:#033&#34;&gt;$TEMP_FILE&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;fi&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;magick &lt;span style=&#34;color:#033&#34;&gt;$TEMP_FILE&lt;/span&gt; img.png&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rm &lt;span style=&#34;color:#033&#34;&gt;$TEMP_FILE&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It uses imagemagick to rename and convert the image to a PNG.&lt;/p&gt;&#xA;&lt;p&gt;There were a few other similar services, but most of what I tried was either a paid service or broken.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.placecats.com/&#34;&gt;PlaceCats&lt;/a&gt; seems workable, if you just want cats. :)&lt;/p&gt;&#xA;&lt;h3 id=&#34;image-search-services&#34;&gt;Image Search Services&lt;/h3&gt;&#xA;&lt;p&gt;Then there are other image services. &lt;a href=&#34;https://unsplash.com/&#34;&gt;Unsplash&lt;/a&gt; and &lt;a href=&#34;https://www.pexels.com/&#34;&gt;Pexels&lt;/a&gt; are two that I&amp;rsquo;ve used in the past extensively - but only through the UI, not API. But both have APIs. And there are plenty of others.&lt;/p&gt;&#xA;&lt;p&gt;At this point, I had ideas in my head that I wanted to build a command line app in Go to do this, so I dug into Pexel&amp;rsquo;s API.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.pexels.com/api/&#34;&gt;https://www.pexels.com/api/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;And went about creating an app.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-getimage-app&#34;&gt;The getimage app&lt;/h2&gt;&#xA;&lt;p&gt;I created an API key, but to be honest, I initially forgot to use it in my request and it worked fine, so&amp;hellip; ? Anyway, I fixed the code and am using the API key now.&lt;/p&gt;&#xA;&lt;p&gt;They do have rate limiting of 200 requests an hour, or 20,000 requests a month. And there&amp;rsquo;s a header to see how many requests you have left. But this header was reporting -1 when I was not using the API key. The header did not exist at all when I was using the key. Again, not sure what&amp;rsquo;s going on with that part of it, but I&amp;rsquo;m not trying to get away with anything and I don&amp;rsquo;t think I&amp;rsquo;ll ever hit up against those limits anyway.&lt;/p&gt;&#xA;&lt;p&gt;You have to do a search first, using some search term. This will give you a list of results. I programmed in a list of likely search terms:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;animals&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;art&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;beach&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;buildings&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;car&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;china&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;city&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fashion&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fire&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;flower&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;food&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;forest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;graffiti&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;holiday&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;horse&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;house&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;japan&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;landscape&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plants&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;restaurant&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shop&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;street&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;woman&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;woods&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you run the program it just randomly chooses one of those.&lt;/p&gt;&#xA;&lt;p&gt;You get back a page of results that includes 15 images by default. You can request a larger page size, but that works for me. I then randomly choose one of those results.&lt;/p&gt;&#xA;&lt;p&gt;Each image has a number of different version (sizes). Generally like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;original&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;large2x&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;large&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;medium&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;small&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;portrait&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;landscape&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tiny&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I found that &lt;code&gt;large2x&lt;/code&gt; were generally the right size for me. At least 900-1000px in each dimension. So I pick that one out. The &lt;code&gt;original&lt;/code&gt; size was way too large.&lt;/p&gt;&#xA;&lt;p&gt;I then load that image using the URL supplied in the JSON.&lt;/p&gt;&#xA;&lt;p&gt;I then shell out to imagemagick to do the resizing and conversion. So you do need to have imagemagick installed. There is an imagemagick library with Go bindings I could use to make this more self-contained. And maybe I&amp;rsquo;ll look into that eventually.&lt;/p&gt;&#xA;&lt;p&gt;Resizing was the trickiest part, but I worked it out with a two-pass system. I make one call to scale the original image to fill the entire space of the requested size. This usually leaves the image too large in one dimension. A second imagemagick call crops it down to the right size. I use a &amp;ldquo;gravity&amp;rdquo; of &amp;ldquo;center&amp;rdquo; when cropping, which usually keeps the main subject of the image, rather than cropping half of it out.&lt;/p&gt;&#xA;&lt;h3 id=&#34;making-it-better&#34;&gt;Making it better&lt;/h3&gt;&#xA;&lt;p&gt;Up do now this worked perfectly for my needs. I&amp;rsquo;d get a random image sized to 800x800, converted to PNG and saved as &amp;ldquo;image.png&amp;rdquo; just by calling &lt;code&gt;getimage&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;But I can do better than that. What is a command line app without parameters?&lt;/p&gt;&#xA;&lt;p&gt;The size was the first part. Pretty easy. Width and height were hardcoded as 800x800. I just added command line params &lt;code&gt;-w&lt;/code&gt; and &lt;code&gt;-h&lt;/code&gt;. They&amp;rsquo;re optional. Both default to 800.&lt;/p&gt;&#xA;&lt;p&gt;I also added a &lt;code&gt;-g&lt;/code&gt; param for geometry. This is just a shorcut to &lt;code&gt;-w&lt;/code&gt; and &lt;code&gt;-h&lt;/code&gt;. If you want a 400x400 image, just say &lt;code&gt;getimage -g 400x400&lt;/code&gt;. Geometry will override the width and height settings if you use both.&lt;/p&gt;&#xA;&lt;p&gt;Then image name. I was defaulting to &amp;ldquo;image.png&amp;rdquo; but added a &lt;code&gt;-o&lt;/code&gt; parameter for the output name. I started adding logic to allow you to specify different file types, such as gif, jpg, etc. But I realized that since I&amp;rsquo;m running it through imagemagick, the extension on the output name specifies the output format as well. Free functionality!&lt;/p&gt;&#xA;&lt;p&gt;Next was search term. Added an &lt;code&gt;-s&lt;/code&gt; param to allow you to search for, say &amp;ldquo;tigers&amp;rdquo; or &amp;ldquo;airplanes&amp;rdquo; or whatever you want, rather than the random search I set up. I can&amp;rsquo;t guarantee you&amp;rsquo;ll get a result, but it&amp;rsquo;ll try.&lt;/p&gt;&#xA;&lt;p&gt;Then at one point I wanted to grab a specific image I saw somewhere and was annoyed that I&amp;rsquo;d have to download it, name it, resize it, convert it. So I added a &lt;code&gt;-u&lt;/code&gt; param. This lets you specify a URL directly to an image. If you specify this, it will bypass the search functionality, attempt to download the given image, then do the resizing, renaming, converting.&lt;/p&gt;&#xA;&lt;p&gt;To summarize:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Usage of getimage:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -g string&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#x9;geometry of image as widthxheight&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -h int&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#x9;height of image (default 800)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -o string&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#x9;the name (and file type) to save the image as (default &amp;#34;image.png&amp;#34;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -s string&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#x9;term to search for&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -u string&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#x9;a direct URL to an image - will bypass search, downoad and resize image&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -w int&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#x9;width of image (default 800)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m pretty damn happy with this. Even in just a few days, I&amp;rsquo;ve used this regularly.&lt;/p&gt;&#xA;&lt;h2 id=&#34;final-words&#34;&gt;Final words&lt;/h2&gt;&#xA;&lt;p&gt;If you use this, be responsible. Get an API key and don&amp;rsquo;t abuse it with too many requests. Also, look at Pexel&amp;rsquo;s attribution rules. If you wind up using the images for anything other than test fodder, follow those rules.&lt;/p&gt;&#xA;&lt;p&gt;I think this would be pretty easy to adapt to use other services such as Unsplash. Looking over their API, it would only require very minor changes in the JSON parsing. Maybe I&amp;rsquo;ll make that an option at some point.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve also thought about making a config file where you can specify your own defaults, search terms, etc. Provider could be part of that.&lt;/p&gt;&#xA;&lt;p&gt;Source and a bit more info here:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://codeberg.org/bit101/getimage&#34;&gt;https://codeberg.org/bit101/getimage&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;&#xA;&lt;p&gt;If you have Go installed on your system, it&amp;rsquo;s as easy as:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go install codeberg.org/bit101/getimage@latest&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll also have to set the environmental variable &lt;code&gt;PEXELS_API_KEY&lt;/code&gt; with your actual API key value.&lt;/p&gt;&#xA;&lt;p&gt;And, as mentioned, installing imagemagick is a must.&lt;/p&gt;&#xA;&lt;p&gt;Tested on Linux, MacOS, and even Windows!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Contours</title>
      <link>https://bit-101.com/blog/posts/2025-12-31/contours/</link>
      <pubDate>Wed, 31 Dec 2025 08:36:19 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-12-31/contours/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;I always have different ideas in the back of my head - techniques that I am aware of and want to implement someday but haven&amp;rsquo;t gotten around to. Drawing contour lines on some kind of height field has been on that list of ideas for at least a couple of years now. I&amp;rsquo;ve made some attempts to figure it out on my own, with some level of success, but never really accomplished anything worth talking about.&lt;/p&gt;&#xA;&lt;p&gt;Just so we&amp;rsquo;re on the same page, here&amp;rsquo;s what I&amp;rsquo;m talking about:&lt;/p&gt;&#xA;&lt;p&gt;You have some kind of 2D field data. It could be elevation data from a government survey, or satellite imagery, or a Perlin/Simplex noise field, a grayscale image meant to encode height/distance, or some other kind of algorithmically generated data. Let&amp;rsquo;s say this is in a 2D array of floats, so you can get any value by saying &lt;code&gt;data[x][y]&lt;/code&gt;. If this is noise data, it could be directly rendered like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;This was rendered with a resolution of 10 pixels, which is why it&amp;rsquo;s blocky. Good enough for our purposes though. Although this is rendered visually, think of it as a 2D array of values, like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;In this case, I have it set so that each value in the array is from 0 to 255, but it can be whatever you want.&lt;/p&gt;&#xA;&lt;p&gt;What we want to do is draw contour lines for all the data points at a certain &amp;ldquo;height&amp;rdquo; like you&amp;rsquo;d see on a topographic map. Like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_03.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;the-algorithm&#34;&gt;The Algorithm&lt;/h2&gt;&#xA;&lt;p&gt;A couple of weeks ago I was looking over the links on &lt;a href=&#34;https://www.mathling.com/art.html&#34;&gt;Mary Holstege&amp;rsquo;s site&lt;/a&gt; (a great resource by the way) and ran across &lt;a href=&#34;https://www.mathling.com/art/20220505_contours.html&#34;&gt;this post on contours&lt;/a&gt; which led me to &lt;a href=&#34;https://paulbourke.net/papers/conrec/&#34;&gt;Paul Bourke&amp;rsquo;s CONREC post&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Side note: if you&amp;rsquo;re not aware of Paul Bourke, it&amp;rsquo;s worth diving deep into his site. So much great stuff there.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, after reading up on the technique, which I generally understand, I ported Bourke&amp;rsquo;s C code to Go. There was actually a sample Go implemention listed there, but I always like to write stuff myself and make it my own rather than just importing libraries when possible. I wouldn&amp;rsquo;t do that on a job necessarily, but that&amp;rsquo;s my strategy for my creative coding efforts.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not going to go in depth into the algorithm itself. But in short, it divides the field into triangles and for each triangle checks how many points are above, below, or on the current contour level being drawn. Based on that, it can generate two points that serve as one tiny segment of the contour line. When you draw all of those segments, you get the contour lines as seen above.&lt;/p&gt;&#xA;&lt;p&gt;And you have to do this for each contour level you want to map. For example, here is the single contour for a value of 128, middle gray in the noise field:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And here it is for the value of 200, which is a much lighter area:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The CONREC formula has you pass an array of contour values that it will iterate over. In the first contour image above, I passed an array of 10 values from 0 to 255. Here it is with 20 contour levels.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;So that&amp;rsquo;s the basic algorithm. Now I&amp;rsquo;ll go into a bit more of what I added to it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;my-take&#34;&gt;My Take&lt;/h2&gt;&#xA;&lt;p&gt;My Go implementation of CONREC was mostly a straight port of the C code. I did make some changes to make it more Go idiomatic, and tried to simplify a few aspects.&lt;/p&gt;&#xA;&lt;h3 id=&#34;makelevels&#34;&gt;MakeLevels&lt;/h3&gt;&#xA;&lt;p&gt;One of the first things I did was to make a helper function for creating the levels array. So I can call &lt;code&gt;makeLevels(count, min, max)&lt;/code&gt; and it will generate an array of the given size with values in the given range. For example, running &lt;code&gt;makeLevels(10, 0, 255)&lt;/code&gt; which is what created the first contour image above, results in the array &lt;code&gt;[0 28.333333333333332 56.666666666666664 85 113.33333333333333 141.66666666666669 170 198.33333333333334 226.66666666666666 255]&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;segments-to-paths&#34;&gt;Segments to Paths&lt;/h3&gt;&#xA;&lt;p&gt;This all worked fine, but one thing I was not thrilled about was the fact that each of these segments was drawn all by itself. They were rendered in grid order, and by contour level order, so there was no concept of one of those contour loops being a single entity. I wanted to turn this into a list of paths, where each path was a list of 2D points I could then connect, maybe changing the color or thickness of, transforming in some way as needed. That wasn&amp;rsquo;t possible in the code as given.&lt;/p&gt;&#xA;&lt;p&gt;So I changed it up so that instead of drawing each segment as soon as it was found, I created segment objects composed of two 2D points. So that for each contour level, there was a list of segment objects relating to that level.&lt;/p&gt;&#xA;&lt;p&gt;But these were still unordered lists of segments, not continuous paths. The next part was the biggest challenge, and the most rewarding.&lt;/p&gt;&#xA;&lt;p&gt;For each contour level, I had a list of segments that I wanted to turn into a list of paths. For example, looking again at contour level 128,&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;&amp;hellip; you can see that there are six discrete paths there. Each one starts and ends on one edge of the canvas. For contour level 200,&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;&amp;hellip; there are eight paths. Some of them intersect and edge, some just loop back to themselves, a couple almost join (or maybe there&amp;rsquo;s three there&amp;hellip; or maybe they overlap. I&amp;rsquo;m not sure.)&lt;/p&gt;&#xA;&lt;p&gt;What I ended up doing:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Take the first segment off the list and add its two points to a new path.&lt;/li&gt;&#xA;&lt;li&gt;Loop through the rest of the segments in the list.&lt;/li&gt;&#xA;&lt;li&gt;For each segment, if one of its points was equal to the last point in the current path, append the OTHER point to the end of the path.&lt;/li&gt;&#xA;&lt;li&gt;Or, if one of its points was equal to the first point in the current path, prepend the OTHER point to the start of the path.&lt;/li&gt;&#xA;&lt;li&gt;In either case, remove that segment from the segment list.&lt;/li&gt;&#xA;&lt;li&gt;Keep doing this until you find no more matches.&lt;/li&gt;&#xA;&lt;li&gt;Save the current path to list of paths, and repeat starting at 1 above.&lt;/li&gt;&#xA;&lt;li&gt;When the segment list is empty, you now have a list of sequential paths.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;simplifying-paths&#34;&gt;Simplifying Paths&lt;/h3&gt;&#xA;&lt;p&gt;As is, the algorithm generates a whole lot of segments, which means each path contains a whole lot of points. For the above example with 10 contour levels, there were a total of 5,794 points in all the paths. That&amp;rsquo;s not horrible, but a 10-pixel resolution is kind of chunky. As you increase the resolution, the number of points explodes. And most of those points are very, very close together, or almost directly in the same line, so they are not adding a lot of value.&lt;/p&gt;&#xA;&lt;p&gt;Fortunately, I had just tackled this problem! See my post from just a few weeks ago, &lt;a href=&#34;https://bit-101.com/blog/posts/2025-11-28/simplifying-paths/&#34;&gt;Simplifying Paths&lt;/a&gt;. So this was a no brainer. Once I created each path as just described, I ran &lt;code&gt;autosimplify&lt;/code&gt; on it. This brought the total number of points in the example down from 5,794 to 1,812. And in some cases it actually looks better, because sometimes when you draw a path through very close points you can get some artifacting.&lt;/p&gt;&#xA;&lt;h3 id=&#34;levels-or-paths&#34;&gt;Levels or Paths&lt;/h3&gt;&#xA;&lt;p&gt;In many cases, all I wanted to do was draw each path as shown above. But I might want to do something different for the set of paths in each level. Maybe color them differently. To accomodate both of these needs, the return value from my contour function returns an object with two properties. One is a flat list of all the paths. I can loop through and draw them all as per above. The other one is an array of levels. Within each level element is the list of paths for that level. So I can now do something different per level now. Here, I colored each one differently:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here, all the orange lines are at low levels. The dark blues are at higher levels.&lt;/p&gt;&#xA;&lt;p&gt;Or, I can generate elevation markers like you&amp;rsquo;d see on an actual topographic map.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;At this point, I feel like I should remind the reader that we&amp;rsquo;ve been working in 10-pixel resolution this whole time, and as mentioned, it&amp;rsquo;s a bit chunky, but good enough for a demo. But you can change that resolution and make everything much smoother:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_09.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;generating-height-data&#34;&gt;Generating Height Data&lt;/h2&gt;&#xA;&lt;p&gt;As described, this method takes in a 2D array of floats representing height data. But how do you generate that data? Obviously, you can generate it algorithmically using Perlin or Simplex noise or some other technique. Or you might be able to download some elevation data somewhere. But a common use case is to pull the height data from an existing bitmap image.&lt;/p&gt;&#xA;&lt;p&gt;To that end I created a few new functions that can generate a height array from an existing image on disk, or from the current state of the canvas. In fact, that&amp;rsquo;s what I&amp;rsquo;ve been doing in the above examples - drawing the noise field and then pulling the height data from that.&lt;/p&gt;&#xA;&lt;p&gt;To test that, I downloaded this random height map image from the web:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_10.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I loaded it in and adjusted the gamma somewhat to make a it a bit brighter.&lt;/p&gt;&#xA;&lt;p&gt;I used my new height data function go get the levels and used that data to generate the contour lines:&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_11.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Upping the number of countour levels to 32 really brings out details. Note, this was done with 1-pixel resolution.&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-12-31/image_12.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I will say that at 1-pixel res and 32 levels, this took a few seconds to render (around 5). So, there&amp;rsquo;s room for optimization, but all in all not horrible as it is.&lt;/p&gt;&#xA;&lt;h2 id=&#34;resources&#34;&gt;Resources&lt;/h2&gt;&#xA;&lt;p&gt;All my code for this is in my &lt;a href=&#34;https://codeberg.org/bit101/bitlib&#34;&gt;bitlib&lt;/a&gt; and &lt;a href=&#34;https://codeberg.org/bit101/blcairo&#34;&gt;blcairo&lt;/a&gt; repos. Specifically:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://codeberg.org/bit101/bitlib/src/branch/main/geom/contour.go&#34;&gt;contour.go&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://codeberg.org/bit101/blcairo/src/commit/e6277ada13840da6264874b2683a15fa01783900/context_process.go#L1206&#34;&gt;context_process.go&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In addition to that, there was another article that I&amp;rsquo;d bookmarked a while ago on the subject but never got around to reading: &lt;a href=&#34;https://blog.bruce-hill.com/meandering-triangles&#34;&gt;Meandering Triangles&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Just as I was sitting down to write this post, I pulled it up and scanned through it. I was pleased to see that the main algorithm was roughly the same as CONREC, but more pleased to see that the author had converted the points into paths in fashion quite similar to what I had done. I&amp;rsquo;m going to have to read this one more deeply, because I feel there may be some good things to learn there, and improve my own code.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>2025 Retrospective</title>
      <link>https://bit-101.com/blog/posts/2025-12-25/2025-retrospective/</link>
      <pubDate>Thu, 25 Dec 2025 16:00:04 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-12-25/2025-retrospective/</guid>
      <description>&lt;p&gt;Well, here we are at the end of another year. Time to reflect on what went well, what didn&amp;rsquo;t, what I will do different next time.&lt;/p&gt;&#xA;&lt;h2 id=&#34;creation&#34;&gt;Creation&lt;/h2&gt;&#xA;&lt;p&gt;Yes, I created some stuff&amp;hellip;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Twenty-six posts on this blog.&lt;/li&gt;&#xA;&lt;li&gt;Forty-two posts on &lt;a href=&#34;https://www.artfromcode.com/&#34;&gt;https://www.artfromcode.com/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Twenty-four tracks on SoundCloud: &lt;a href=&#34;https://soundcloud.com/bit101&#34;&gt;https://soundcloud.com/bit101&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;music&#34;&gt;Music&lt;/h3&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s start with the last one, music. For a few years I wanted to get to the points where I could execute a song and release it into the wild for people to listen to. In fact, in my &lt;a href=&#34;https://www.bit-101.com/2017/2022/12/2022-in-review/&#34;&gt;2022 Retrospective&lt;/a&gt; one of my goals was to release a song. I did actually put out a &amp;ldquo;song&amp;rdquo; here and there since then. Often just some random tone generation or something like that. In 2025 I published 24 songs on SoundCloud. Actual songs with structure and different instruments. Not saying they were amazing, but they still make me happy when I hear them.&lt;/p&gt;&#xA;&lt;p&gt;These were mostly created with &lt;a href=&#34;https://www.renoise.com/&#34;&gt;Renoise&lt;/a&gt; and the &lt;a href=&#34;https://dirtywave.com/&#34;&gt;M8 Tracker&lt;/a&gt;. This really kicked off in January when I was bedridden and quarantined with my first bout of Covid. I tried out Renoise and loved it. Loved the tracker paradigm and got an M8, which was really just completely amazing. I was very deeply into creating music for most of the first half of the year and then somehow just burnt out.&lt;/p&gt;&#xA;&lt;p&gt;Creating music is a lot of fun and really rewarding. I&amp;rsquo;m extremely happy that I got to the point where I did with it, and that I had enough confidence to release the stuff I did. For my entire life I felt that the inability to do anything musically was a horrible failing. So I&amp;rsquo;m over that now. At the same time, I realized it&amp;rsquo;s not my calling. I&amp;rsquo;m sure I&amp;rsquo;ll continue to dabble in it now and then and create some tunes once in a while. But visual graphics and animation is my real passion. I hope to combine the two more in the future.&lt;/p&gt;&#xA;&lt;h3 id=&#34;3d-creation&#34;&gt;3D Creation&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not really a huge 3D guy. I do love creating retro-3D effects in wireframe or point clouds, but full-on 3D rendering has never been my outlet. That said, &lt;a href=&#34;https://www.bit-101.com/2017/2022/10/my-raytracing-journey/&#34;&gt;I did create my own 3D raytracer from scratch in 2022&lt;/a&gt;. And that was one of the most rewarding things I&amp;rsquo;ve done programming-wise. For some reason this year I was reminded of &lt;a href=&#34;https://www.povray.org/&#34;&gt;POV-Ray&lt;/a&gt; which I played with back in the day. It&amp;rsquo;s still out there and functions perfectly. So I installed it and had fun for a few weeks. I had a bit of a go trying to create a wrapper for POV in Go. I had some success, but in the end it was over-engineered and I went native and just started coding scenes by hand. Most of the problems I was trying to solve with the wrapper had better solutions built-in. It just took a while to find them. It didn&amp;rsquo;t go very far, but it was probably good for a month of fun. Here&amp;rsquo;s some more words on that subject:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-06-14/pov-ray/&#34;&gt;https://bit-101.com/blog/posts/2025-06-14/pov-ray/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;daily-code&#34;&gt;Daily Code&lt;/h3&gt;&#xA;&lt;p&gt;In October, I was feeling the urge to do some daily coding. I created my own hashtag, &lt;code&gt;#octanimate&lt;/code&gt; and posted something every day in October. Here are the results:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-19/octanimate-2025-part-i/&#34;&gt;https://bit-101.com/blog/posts/2025-10-19/octanimate-2025-part-i/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-23/octanimate-2025-part-ii/&#34;&gt;https://bit-101.com/blog/posts/2025-10-23/octanimate-2025-part-ii/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-11-01/octanimate-part-iii/&#34;&gt;https://bit-101.com/blog/posts/2025-11-01/octanimate-part-iii/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;When October was over, I wasn&amp;rsquo;t ready to stop, so just continued on daily for an additional 39 days, using the tag &lt;code&gt;#dailycoding&lt;/code&gt;. Those are all over on Art From Code: &lt;a href=&#34;https://www.artfromcode.com/tags/dailies/&#34;&gt;https://www.artfromcode.com/tags/dailies/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;After 70 straight days of posting, I was satisfied for a while.&lt;/p&gt;&#xA;&lt;p&gt;I also took place in &lt;a href=&#34;https://adventofcode.com/&#34;&gt;Advent of Code&lt;/a&gt;. This year was different. They went from 25 days down to 12 and killed the global leaderboard. This was a good change in my opinion. I still didn&amp;rsquo;t finish though. I got through day nine and had a blast, but I looked at the instructions for day 10 and said, &amp;ldquo;I&amp;rsquo;m done.&amp;rdquo; Maybe I&amp;rsquo;ll go back and try to wrap up the last three days at some point, but I&amp;rsquo;m good for now.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://genuary.art/&#34;&gt;Genuary 26&lt;/a&gt; is coming up in a week or so. I think I&amp;rsquo;ll abstain this year though. I&amp;rsquo;ve had good fun with these prompt-based challenges in the past, but in general, I&amp;rsquo;m not crazy about them. I have a lot of ideas on my own that I haven&amp;rsquo;t gotten around to. A lot of the prompts are interesting, but things I&amp;rsquo;ve already done. Some are just things that I have no interest in doing. I&amp;rsquo;ll probably look at them each day and if something sparks an interest I might give it a go.&lt;/p&gt;&#xA;&lt;h2 id=&#34;personal-tech&#34;&gt;Personal Tech&lt;/h2&gt;&#xA;&lt;h3 id=&#34;nas--home-server&#34;&gt;NAS / Home Server&lt;/h3&gt;&#xA;&lt;p&gt;I revamped a lot of my personal technology this past year. Earlier in the year I had a home server consisting of an thin HP client running Ubuntu Server. I had Plex, and then later Jellyfin running on it, some other personal services, using Wireguard ported through an external VPS.&lt;/p&gt;&#xA;&lt;p&gt;This summer I upgraded all of that. I got a larger used HP box and installed TrueNAS on it, reused the older one as a backup server. Switched over to Tailscale from Wireguard. I spent a lot of time scheduling backups (local and cloud sync remote), S.M.A.R.T. tests, snapshots, scrubs, and set up all kinds of monitoring. More details here:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-07-20/i-have-a-nas/&#34;&gt;https://bit-101.com/blog/posts/2025-07-20/i-have-a-nas/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-08-23/nas-part-ii/&#34;&gt;https://bit-101.com/blog/posts/2025-08-23/nas-part-ii/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Since then I&amp;rsquo;ve also moved my RSS reader over to a self-hosted &lt;a href=&#34;https://www.freshrss.org/&#34;&gt;FreshRSS&lt;/a&gt; install and my &amp;ldquo;read it later&amp;rdquo; stuff to a self-hosted &lt;a href=&#34;https://linkding.link/&#34;&gt;LinkDing&lt;/a&gt;. I&amp;rsquo;m really, really happy with both of those.&lt;/p&gt;&#xA;&lt;p&gt;It was my intention to move the whole rig over to a dedicated NAS case with a low power motherboard early in 2026. But since it&amp;rsquo;s working fine as it is, I may postpone that a bit. At least to see if the price of RAM goes down.&lt;/p&gt;&#xA;&lt;h3 id=&#34;git&#34;&gt;Git&lt;/h3&gt;&#xA;&lt;p&gt;I fully migrated off of github. I was not one of those people who freaked out when Microsoft acquired github, but I don&amp;rsquo;t like the direction they&amp;rsquo;re headed right now and did not feel great about having my code there. After looking around, I settled on &lt;a href=&#34;https://codeberg.org/&#34;&gt;Codeberg&lt;/a&gt;. They seem like an amazing company and I feel really good about that move. I also set up a &lt;a href=&#34;https://forgejo.org/&#34;&gt;Forgejo&lt;/a&gt; install on my NAS, for hosting private repos. It feels really good to self host this. I did have a bit of concern over being fully responsible for maintaining this. But I have to trust in the work I did on my NAS - double backups, snapshots, scrubs, tests, monitoring, alerting. It&amp;rsquo;s the most responsible data protection I&amp;rsquo;ve ever had set up.&lt;/p&gt;&#xA;&lt;p&gt;More info on that whole git migration &lt;a href=&#34;https://bit-101.com/blog/posts/2025-09-07/github/&#34;&gt;here&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;personal&#34;&gt;Personal&lt;/h2&gt;&#xA;&lt;p&gt;Overall, going pretty well.&lt;/p&gt;&#xA;&lt;p&gt;Work is fine. I think. One never knows these days, right? I think it&amp;rsquo;s good. I hope it&amp;rsquo;s good because I&amp;rsquo;m in my 60s now and the prospects of getting a new tech job at my age feel kind of sketchy. In the decades I&amp;rsquo;ve been in tech, and the many companies I&amp;rsquo;ve been in, I&amp;rsquo;ve never seen anyone my age hired into a technical role. I might have a chance as a manager. But for all the activism around various forms of discrimination (activism which I support), it&amp;rsquo;s still open season for ageism. I&amp;rsquo;ve heard age-related comments that might get someone fired if they said those things about any other group, but for age nobody bats an eye.&lt;/p&gt;&#xA;&lt;p&gt;Health is fine. I wound up with a strangely large number of medical visits this past year. A couple of procedures, nothing serious at all, one dental, one cosmetic. And lots of follow ups and tests for previous concerns. Any time they look too closely, they find something else they want to look at. And when they look at that, they see another thing they want to check. But I worked through that whole cycle got all the tests done and have a clean bill of health at this point.&lt;/p&gt;&#xA;&lt;h2 id=&#34;next-big-project---prints&#34;&gt;Next Big Project - Prints!&lt;/h2&gt;&#xA;&lt;p&gt;In recent weeks, I&amp;rsquo;ve embarked upon a new project, which is creating physical paper-and-ink prints of some of the stuff I create. I&amp;rsquo;ve repurposed &lt;a href=&#34;https://keithpeters.org&#34;&gt;keithpeters.org&lt;/a&gt; for that (was previously hosting my music). I&amp;rsquo;ve done some test prints and I&amp;rsquo;m really happy how they&amp;rsquo;ve come out. I&amp;rsquo;m doing a lot of other groundwork to set this up and some time very early next year I&amp;rsquo;ll have more to say about this.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Drawing Rotated Shapes</title>
      <link>https://bit-101.com/blog/posts/2025-12-04/drawing-rotated-shapes/</link>
      <pubDate>Thu, 04 Dec 2025 09:15:09 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-12-04/drawing-rotated-shapes/</guid>
      <description>&lt;p&gt;Just a quick one to verbalize a choice I often make but never thought much about.&lt;/p&gt;&#xA;&lt;p&gt;Say you want to draw a 100-pixel line, starting at point A, and extending at an angle of 30 degrees (PI/6 radians).&lt;/p&gt;&#xA;&lt;p&gt;A. You can do it with math. Using trig to find the end point of the line. Here&amp;rsquo;s some pseudocode, but should work similarly in many systems.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.moveto(pointA.x, pointA.y)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.lineto(pointA.x &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cos(pi &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;6&lt;/span&gt;) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;, pointA.y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; sin(pi &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;6&lt;/span&gt;) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;B. You can transform the canvas and draw a 0 degree line to 100, 0.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.save()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.translate(pointA.x, pointA.y)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.rotate(pi &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;6&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.moveto(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.lineto(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.restore()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Stategy B has more steps. You have to save the context&amp;rsquo;s transform and restore it later, translate to the starting point to get things to rotate from that, do the rotation, then draw the line.&lt;/p&gt;&#xA;&lt;p&gt;Strategy A, while fewer lines of code, requires more complex math.&lt;/p&gt;&#xA;&lt;p&gt;Doing a bunch of fairly unscientific profiling tests in cairographics with Go bindings, the trig method generally wins out slightly, but not always. It&amp;rsquo;s pretty close.&lt;/p&gt;&#xA;&lt;p&gt;So I can&amp;rsquo;t make a play on performance. You&amp;rsquo;d have to test your own code in your own platform.&lt;/p&gt;&#xA;&lt;p&gt;I will say though, that if you&amp;rsquo;re drawing multiple shapes all rotated at the same angle from the same point, of course you should be transforming the context and then drawing them all.&lt;/p&gt;&#xA;&lt;p&gt;On the other hand, if the location of that final point is important for some reason - maybe you want to color the line differently if it intersects some other shape - using trig and knowing the untransformed location of that second point would probably be the way to go.&lt;/p&gt;&#xA;&lt;p&gt;But for the simple one-off cases, sometimes the trig method feels right, and sometimes the transform feels like the more obvious choice.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Simplifying Paths</title>
      <link>https://bit-101.com/blog/posts/2025-11-28/simplifying-paths/</link>
      <pubDate>Fri, 28 Nov 2025 14:30:48 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-11-28/simplifying-paths/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;About a year ago I made a post about &lt;a href=&#34;https://bit-101.com/blog/posts/2024-09-29/evenly-placed-points-on-bezier-curves/&#34;&gt;Evenly Placed Points on Bézier Curves&lt;/a&gt;. I&amp;rsquo;ve found that code to be enormously useful. In fact, recently, I expanded on this concept, so I can go from a list of points that create a path like this&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;points making up a path&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-28/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;to a path made of piecewise quadratic Bézier curves like this&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;a closed curve&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-28/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;to a path of points that recreates the curve directly like this&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;a closed curve&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-28/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The second two images may look the same, but one is drawn using quadratic Bézier curves and the other is drawn with a series of points with straight line segments between them. In fact, here&amp;rsquo;s that image drawn again, with only points, not lines. You&amp;rsquo;ll have to look pretty closely to see the points.&lt;/p&gt;&#xA;&lt;img alt=&#34;a closed curve rendered with points&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-28/image_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;As described in the above-mentioned post, the latter is useful for interpolating along the curve, or placing items on the curve, or finding a point on the curve that it closest to some external point, and other things. The functionality I have now lets me go from the list of points in image 1 the the expanded list of points forming the curves in image 3, in one shot.&lt;/p&gt;&#xA;&lt;p&gt;An example is this piece I did a couple of days ago.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-11-28/251126.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;The circles traveling on the edge of the curve are uniformly distributed along that curve and they need to know the exact position of that curve at any point. This would be pretty tough with regular Bézier curves, but with uniformly interpolated points along the curve it&amp;rsquo;s much easier.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-problem&#34;&gt;The Problem&lt;/h2&gt;&#xA;&lt;p&gt;Since my function takes an unknown number of points in unknown positions and forms an unknown number of quadratic Bézier curves of unknown length, it&amp;rsquo;s impossible to know how many points to assign to each curve individually. That is, how many small segments to break each curve into so it closely follows the shape of the curve. Through trial and error I&amp;rsquo;ve found that around 50 points virtually always does a completely suitable job. Fewer than that, and in some complex curves you wind up with sharp corners here and there. So I hard-coded 50 into the function.&lt;/p&gt;&#xA;&lt;p&gt;And in general this has been great but I know in some cases I&amp;rsquo;m winding up with many, many more points than I really need. If I have just 6 points in my original list, that winds up with 6 curves and 300 points in the final path. Again, look at the previous image with the curve rendered only in points. Many of the points are so tightly packed you can barely see them individually.&lt;/p&gt;&#xA;&lt;p&gt;So rather than trying to get the magic number of points up front, I started thinking about how I could simplify the path &lt;em&gt;after the fact&lt;/em&gt;. Remove the extraneous points, keep the necessary ones.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-solution&#34;&gt;The Solution&lt;/h2&gt;&#xA;&lt;p&gt;In transparency, I&amp;rsquo;ve done this kind of thing before in Flash, probably close to a couple of decades ago. I remembered what I did then and tried to recreate it. To pretty good success in fact.&lt;/p&gt;&#xA;&lt;p&gt;The concept is pretty simple. I loop through my point list, looking at groups of three consecutive points, &lt;code&gt;points[0]&lt;/code&gt;, &lt;code&gt;points[1]&lt;/code&gt; and &lt;code&gt;points[2]&lt;/code&gt; for example. Call them &lt;code&gt;pA&lt;/code&gt;, &lt;code&gt;pB&lt;/code&gt; and &lt;code&gt;pC&lt;/code&gt;. Then I test if these three points are collinear. In other words, do they fall on the same line? If so, then &lt;code&gt;pB&lt;/code&gt; isn&amp;rsquo;t adding anything to the curve at all. I could just draw a line from &lt;code&gt;pA&lt;/code&gt; to &lt;code&gt;pC&lt;/code&gt;, ignoring &lt;code&gt;pB&lt;/code&gt; altogether and you wouldn&amp;rsquo;t know the difference. So I remove &lt;code&gt;pB&lt;/code&gt; from the list and carry on through the rest of the list.&lt;/p&gt;&#xA;&lt;p&gt;Something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; simplify(points) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; points.length &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pA &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pB &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pC &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (colinear(pA, pB, pC)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            points.remove(pB)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Obviously, that&amp;rsquo; pseudocode, and altering a list while iterating through it can get you in trouble. It all worked out for me the way I set it up in Go, but I&amp;rsquo;ll leave it to you to figure out the best way to cull out those middle collinear points in your language and framework.&lt;/p&gt;&#xA;&lt;p&gt;Now of course, it&amp;rsquo;s impossibly rare that you&amp;rsquo;ll ever find three dynamically generated points that are exactly collinear, so we&amp;rsquo;ll allow a little leeway. We&amp;rsquo;ll actually be saying, are these points &lt;em&gt;close enough&lt;/em&gt; to being collinear?&lt;/p&gt;&#xA;&lt;p&gt;So how do you tell if three points are collinear? Well, there are different ways to go about this. The most obvious and direct is to compare the slope of the line from &lt;code&gt;pA&lt;/code&gt; to &lt;code&gt;pB&lt;/code&gt; to the slope between &lt;code&gt;pB&lt;/code&gt; and &lt;code&gt;pC&lt;/code&gt;. That would look something like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; colinear(pA, pB, pC, threshold) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    m0 &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; (pB.y &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; pA.y) &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; (pB.x &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; pA.x)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    m1 &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; (pC.y &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; pB.y) &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; (pC.x &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; pB.x)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    diff &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; abs(m1&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;m0)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; diff &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; threshold&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now you can adjust your threshold value so you&amp;rsquo;re removing the most points possible, but your curve isn&amp;rsquo;t starting to get chunky. I find that a threshold of around 0.1 gives pretty good results. This means that if the two slopes differ by less than 0.1 you can remove the middle point.&lt;/p&gt;&#xA;&lt;p&gt;Visually, this image might help:&lt;/p&gt;&#xA;&lt;img alt=&#34;two sets of three points with slops drawn&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-28/image_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;In the top image, the slop from the first to second point is quite different than the slope from the second to the third. These are very far from collinear.&lt;/p&gt;&#xA;&lt;p&gt;In the bottom image, the slopes are pretty similar. This is just a visual example, but you might be able to say this is close enough to collinear.&lt;/p&gt;&#xA;&lt;p&gt;But I found I got a lot better results with a different method.&lt;/p&gt;&#xA;&lt;h3 id=&#34;alternate-method&#34;&gt;Alternate method&lt;/h3&gt;&#xA;&lt;p&gt;In my library I have a &lt;code&gt;Segment&lt;/code&gt; object, which is formed by two points. And I have a &lt;code&gt;Segment.distanceTo(point)&lt;/code&gt; function that you can pass a point into and it gives you back the distance from that point to the segment. I make a segment with &lt;code&gt;pA&lt;/code&gt; and &lt;code&gt;pC&lt;/code&gt; and get the distance from &lt;code&gt;pB&lt;/code&gt; to that segment. It&amp;rsquo;s something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Javascript&#34; data-lang=&#34;Javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; colinear(pA, pB, pC, threshold) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    segment &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;new&lt;/span&gt; Segment(pA, pC)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dist &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; segment.distanceTo(pB)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; distance &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; threshold&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, I find a threshold of about 0.25 gives good results. In this case it means &lt;code&gt;pB&lt;/code&gt; needs to be less than a quarter of a pixel from the segment formed by &lt;code&gt;pA&lt;/code&gt; and &lt;code&gt;pB&lt;/code&gt; in order to be removed. That&amp;rsquo;s pretty close to collinear.&lt;/p&gt;&#xA;&lt;img alt=&#34;three circles left and right joined by a black line. a red line from the middle one to the black line&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-28/image_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here you can see the first and third points joined by a segment (in black) and the distance from the middle point to that segment in red. In this case, it&amp;rsquo;s pretty close to the segment and might be considered collinear.&lt;/p&gt;&#xA;&lt;p&gt;Btw, you can find my &lt;code&gt;Segment&lt;/code&gt; object here: &lt;a href=&#34;https://codeberg.org/bit101/bitlib/src/branch/main/geom/segment.go&#34;&gt;https://codeberg.org/bit101/bitlib/src/branch/main/geom/segment.go&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;For whatever reason, with this method I can remove a lot more points and still have a much smoother, more accurate curve than I can using the slope method.&lt;/p&gt;&#xA;&lt;p&gt;My theory is that with the slope method, you can have a sequence of points that are very close together. The slopes might differ more than your threshold, so the middle point doesn&amp;rsquo;t get removed. But because the distances are so small, that middle point is visually collinear - maybe only a small fraction of a pixel away. It should be removed.&lt;/p&gt;&#xA;&lt;p&gt;Again a visual might help.&lt;/p&gt;&#xA;&lt;img alt=&#34;three large circles with slopes between them&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-28/image_07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Consider this a very zoomed in view of three consecutive points. The slopes between the two pairs differs by a good amount, but if you consider that each circle is one pixel in diameter, it&amp;rsquo;s only off by a small fraction of a single pixel. If you zoomed out, you&amp;rsquo;d barely see this.&lt;/p&gt;&#xA;&lt;p&gt;So you wind up with not as many points being removed. But if you crank up the threshold to catch those close-up points, you wind up removing too many points in areas where the points are further away and things get chunky there.&lt;/p&gt;&#xA;&lt;p&gt;The distance-to-segment method is much more aggressive on closely spaced points, where you&amp;rsquo;re safe to remove more, but more lenient on larger curves with more space, where you want to keep points for accuracy.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s my theory anyway.&lt;/p&gt;&#xA;&lt;h2 id=&#34;iteration&#34;&gt;Iteration&lt;/h2&gt;&#xA;&lt;p&gt;If you do well here, you could end up removing a good number of points. Maybe even half, depending on the state of your list. But you might be able to do better. Run it again and you might be able to remove a few more. And again, and maybe again a couple more times. Rather than manually calling it a bunch of times, you can add an iteration parameter to your &lt;code&gt;simplify&lt;/code&gt; function. I&amp;rsquo;ll also throw in a &lt;code&gt;threshold&lt;/code&gt; parameter.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; simplify(points, threshold, iter) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (j &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; j &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; iter; j&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt; ) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; points.length &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            pA &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            pB &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            pC &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (colinear(pA, pB, pC, threshold)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                points.remove(pB)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;ve found that on a fairly complex curve, with the distance-to-segment method and a threshold of 0.1, five iterations gets down to the minimal number of points. Anyway, this method gives you full control. But maybe you want something simpler that just works pretty good most of the time.&lt;/p&gt;&#xA;&lt;h2 id=&#34;auto-simplify&#34;&gt;Auto-simplify&lt;/h2&gt;&#xA;&lt;p&gt;I created another method called &lt;code&gt;autosimplify&lt;/code&gt;. This hard-codes the 0.25 and intelligently iterates as much as it needs to.&lt;/p&gt;&#xA;&lt;p&gt;It does this by keeping track of the number of points before and after each iteration. If it&amp;rsquo;s the same, then it&amp;rsquo;s stopped removing any points. You&amp;rsquo;re done. If it removed any, then it will try again and see if it can removed more.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; autosimplify() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    count &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points.length&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;true&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; points.length &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            pA &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            pB &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            pC &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (colinear(pA, pB, pC, &lt;span style=&#34;color:#f60&#34;&gt;0.25&lt;/span&gt;)) { &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// hard-coded threshold&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                points.remove(pB)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (points.length &lt;span style=&#34;color:#555&#34;&gt;==&lt;/span&gt; count) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// we didn&amp;#39;t remove anything this time. done.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        } &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;else&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// we removed something. keep track for next time.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            count &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; points.length&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;stress-test&#34;&gt;Stress Test&lt;/h2&gt;&#xA;&lt;p&gt;To show off the results - both in quality and in point removal, I created this stress test. It starts off with 50 moving randomized points. This creates 50 internal curves with 50 points, for a total of 2,500 points in the final curve. (2,501 actually, since I add an extra one in there to close the loop for&amp;hellip; reasons.) This original path is drawn in blue. Then I run the &lt;code&gt;autosimplify&lt;/code&gt; function as described above, and shift the result over 10 pixels. In the center, I print the number of original and simplified points.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-11-28/stresstest.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;To my eyes, there is little or no difference. And the numbers don&amp;rsquo;t lie. Average reduction is 80%.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;The concept here is, start with more points than you probably need. Then, intelligently cull out the ones you don&amp;rsquo;t need.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m sure there are improvements that can be made here. Maybe a more dynamic &lt;code&gt;colinear&lt;/code&gt; method that takes into account not only the slope but the distance. I&amp;rsquo;m sure this will see some improvements over time.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Runways</title>
      <link>https://bit-101.com/blog/posts/2025-11-24/runways/</link>
      <pubDate>Mon, 24 Nov 2025 06:00:34 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-11-24/runways/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been posting daily coded animations for the past 55 days as of today. You can see my &amp;ldquo;Octanimate&amp;rdquo; series in these posts:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-19/octanimate-2025-part-i/&#34;&gt;Octanimate Part I&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-23/octanimate-2025-part-ii/&#34;&gt;Octanimate Part II&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2025-11-01/octanimate-part-iii/&#34;&gt;Octanimate Part II&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;And what I&amp;rsquo;ve been doing in November here: &lt;a href=&#34;https://www.artfromcode.com/tags/dailies/&#34;&gt;Dailies&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;The latest one of the dailies is here:&lt;/p&gt;&#xA;&lt;video autoplay=&#34;&#34; controls=&#34;&#34; loop=&#34;&#34; tabindex=&#34;-1&#34; title=&#34;&#34;&gt;&#xA;  &lt;source src=&#34;https://artfromcode.com/afc_media/251124/251124.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt;&#xA;&lt;p&gt;I really enjoyed the process of making this and wanted to share some of the process.&lt;/p&gt;&#xA;&lt;p&gt;For a while now I&amp;rsquo;ve had the urge to do some kind of mapping visualization. I&amp;rsquo;d been looking around for open elevation map data sets but didn&amp;rsquo;t find anything easy to get a hold of and work with. The other day I thought, &amp;ldquo;what about just getting the coordinates of every ____ in the country?&amp;rdquo;. It would have to be something that was important enough to be documented and worth someone actually compiling the latitude and longitude of each point.&lt;/p&gt;&#xA;&lt;p&gt;Airports was the first one that came to mind. So I did a search. The first hit I got not only had the location of each airport in the country, but the coordinates of each &lt;em&gt;runway&lt;/em&gt;! Eight thousand, six hundred and twenty four of them to be exact. Here&amp;rsquo;s that link. &lt;a href=&#34;https://geodata.bts.gov/datasets/110af7b8a9424a59a3fb1d8fc69a2172_0/explore?location=42.363561%2C-71.006261%2C14.39&amp;amp;showTable=true&#34;&gt;https://geodata.bts.gov/datasets/110af7b8a9424a59a3fb1d8fc69a2172_0/explore?location=42.363561%2C-71.006261%2C14.39&amp;amp;showTable=true&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;There are multiple formats available for the data. I first went for the GeoJSON format. But this was a bit too much. It actually contained coordinates for the polygon shapes of each runway! I pulled down the CSV version and that was much simpler - the coordinates for the start and end of each runway, basically just a line segment. Here&amp;rsquo;s a single line of that file, representing a single runway at LAX.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;635,2025/10/30,01818.,A,CA,LAX,LOS ANGELES INTL,LOS ANGELES,US,PU,PU,06L/24R,33.94911247,-118.43115986,33.95210391,-118.40194891,8926,150,ASPH-CONC,GOOD,GRVD,,,,,,HIGH,3RD PARTY SURVEY,2010/03/23,120,203,383,880,181220.57421875,6663.48966096801&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The coords for that runway are: (33.94911247,-118.43115986) to (33.95210391,-118.40194891), found in the 13th through the 16th fields there.&lt;/p&gt;&#xA;&lt;p&gt;This was super easy to parse in Go, grab the two points for each runway, do a bit of translation and scaling to center the whole thing and draw each one of those 8,624 runways. And voila!&lt;/p&gt;&#xA;&lt;img alt=&#34;black screen with some white dots in the center looking like the shape of the USA&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-24/zoom_1.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Since a few degrees of lattitude or longitude cover a very wide area, mapping this directly to pixels gives us a tiny little map. But fi I zoom in by say 15x, we&amp;rsquo;re seeing the whole country.&lt;/p&gt;&#xA;&lt;img alt=&#34;black screen with some white dots in the center looking like the shape of the USA&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-24/zoom_15.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now if I pan over to Boston Harbor and zoom in 10,000x, we have the glorious chaos that is Logan International Airport.&lt;/p&gt;&#xA;&lt;img alt=&#34;black screen with some white representing airport runways&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-24/zoom_10k_bos.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And it&amp;rsquo;s easy enough to do that for any location. Here&amp;rsquo;s San Francisco.&lt;/p&gt;&#xA;&lt;img alt=&#34;black screen with some white representing airport runways&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-11-24/zoom_10k_sfo.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Next I wanted to animate it. This took a bit of trial and error. On the one hand, you want to see what some actual runways look like. On the other hand, you want to see the whole mass of them. After trying a whole bunch of things I settled on starting zoomed in on Boston at 4000x, slowly zooming out to 25x then panning over to SFO, and finally zooming back in. Stuck some labels on there and a bit of fading in and out and it&amp;rsquo;s a wrap.&lt;/p&gt;&#xA;&lt;p&gt;This was a really fun project and really just took a couple hours one night and a bit of touch up the next morning. Getting, parsing and drawing the data was easy. The zooming into a specific location and the labels were a bit tricky, and getting the animation right consumed the most of the time. I&amp;rsquo;m looking forward to finding other data sets like this that I can play with.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Daily Coding</title>
      <link>https://bit-101.com/blog/posts/2025-11-15/daily-coding/</link>
      <pubDate>Sat, 15 Nov 2025 16:13:06 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-11-15/daily-coding/</guid>
      <description>&lt;p&gt;I had such a fun time doing &lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-19/octanimate-2025-part-i/&#34;&gt;Octanimate&lt;/a&gt; &lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-23/octanimate-2025-part-ii/&#34;&gt;last&lt;/a&gt; &lt;a href=&#34;https://bit-101.com/blog/posts/2025-11-01/octanimate-part-iii/&#34;&gt;month&lt;/a&gt; that I just kept going.&lt;/p&gt;&#xA;&lt;p&gt;I didn&amp;rsquo;t come up with a catchy tag for November, so I&amp;rsquo;ve just been using &lt;a href=&#34;https://mstdn.social/tags/dailycoding&#34;&gt;#dailycoding&lt;/a&gt; on mastodon (others use the tag too). I&amp;rsquo;m not going to post those on this site, because I&amp;rsquo;ve also been keeping up with them over here: &lt;a href=&#34;https://www.artfromcode.com/tags/dailies/&#34;&gt;https://www.artfromcode.com/tags/dailies/&lt;/a&gt;. I don&amp;rsquo;t know how long I&amp;rsquo;ll keep thig soing, but it&amp;rsquo;s fun so far.&lt;/p&gt;&#xA;&lt;p&gt;In October, my theme was noise. Not intentionally, I just wound up doing a lot of Simplex noise based stuff. This month, I&amp;rsquo;m working a lot with particles. I used to be obsessed with particles systems in my early Flash days. I haven&amp;rsquo;t done much in a while, so I started building out a particle system module that will eventually wind up in my own library. But you&amp;rsquo;ll see some of the pieces there use it already.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Octanimate Part III</title>
      <link>https://bit-101.com/blog/posts/2025-11-01/octanimate-part-iii/</link>
      <pubDate>Fri, 31 Oct 2025 15:55:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-11-01/octanimate-part-iii/</guid>
      <description>&lt;p&gt;Day 21:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251021.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 22:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251022.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 23:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251023.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 24:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251024.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 25:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251025.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 26:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251026.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 27a:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251027a.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 27b:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251027b.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 27c:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251027c.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 28:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251028.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 29:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251029.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 30:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251030.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 31:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-31/251031.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Octanimate 2025 Part II</title>
      <link>https://bit-101.com/blog/posts/2025-10-23/octanimate-2025-part-ii/</link>
      <pubDate>Thu, 23 Oct 2025 18:48:49 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-10-23/octanimate-2025-part-ii/</guid>
      <description>&lt;p&gt;To recap, &lt;code&gt;#octanimate&lt;/code&gt; is just my personal hashtag for posting an animation every day in October. You can follow me on &lt;a href=&#34;https://mstdn.social/@bit101&#34;&gt;mastodon&lt;/a&gt; if you&amp;rsquo;re there (you should be there, btw). Or you can just view the entries in a few groups here. A few days ago I posted the &lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-19/octanimate-2025-part-i/&#34;&gt;first ten entries&lt;/a&gt;. Here are the next ten.&lt;/p&gt;&#xA;&lt;p&gt;Day 11:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251011.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 12:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251012.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 13:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251013.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 14:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251014.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 15:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251015.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 16:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251016.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 17:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251017.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 18:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251018.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 19:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251019.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 20:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251020.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;See you in another week and a half or so for the final group!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Proximity Webs Across Point Sets</title>
      <link>https://bit-101.com/blog/posts/2025-10-21/proximity-webs-across-point-sets/</link>
      <pubDate>Tue, 21 Oct 2025 16:43:41 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-10-21/proximity-webs-across-point-sets/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;I posted this animation on Mastodon today, as day 21 of &lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-19/octanimate-2025-part-i/&#34;&gt;Octanimate&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;short line segments rotating and forming various patterns&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-21/251021.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I didn&amp;rsquo;t think too much about it, but it got more likes than I expected, and quite a few comments or questions about what exactly was going on there. I threw out a few quick one line answers there but thought it might be worth more of an explanation.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s actually a pretty neat technique that I came up with just on a usual &amp;ldquo;what if&amp;hellip;?&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;Specifically, what if I did a &lt;a href=&#34;https://bit-101.com/blog/posts/2025-10-12/proximity-webs/&#34;&gt;proximity web&lt;/a&gt; not just on a single set of points, but on two sets of points. Even more specifically, I &lt;em&gt;wouldn&amp;rsquo;t&lt;/em&gt; compare each point in a set to all the other points in the same set, but I&amp;rsquo;d compare each point in set A with all the points in set B. If they were within a certain distance, draw a line between the two points. And then move the points in each set around in some way.&lt;/p&gt;&#xA;&lt;h2 id=&#34;step-by-step&#34;&gt;Step by Step&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;First we take a grid of points. Pretty basic. These are 20 pixels apart on each axis.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;img alt=&#34;grid of points&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-21/image_01.png&#34; /&gt;&#xA;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Now we make another grid of points. Spoiler: it looks exactly the same as the first grid.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Now we rotate each grid, one clockwise, the other counter-clockwise (or anti-clockwise if that&amp;rsquo;s how you roll).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;rotating grids of points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-21/movie_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Already this is creating some interesting patterns. But let&amp;rsquo;s keep going.&lt;/p&gt;&#xA;&lt;ol start=&#34;4&#34;&gt;&#xA;&lt;li&gt;Then, as described above, we&amp;rsquo;ll take each point in grid A and compare it to each point in grid B. If they are less than 28 pixels apart, we draw a line between them. The line&amp;rsquo;s width is inversely proportional to its length. Why 28? That&amp;rsquo;s roughly the square root of two times 20 (distance between points in each grid). So the connecting lines will almost but not quite reach across the diagonals when the grids are aligned. I figured more than that was a bit too busy, less was too sparse. You may have to zoom in to see the lines if you&amp;rsquo;re on a phone or other small device.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;rotating grids of points, connected by lines&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-21/movie_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Again, this is creating an interesting pattern. But a bit too rigid for my taste.&lt;/p&gt;&#xA;&lt;ol start=&#34;5&#34;&gt;&#xA;&lt;li&gt;Randomize each grid. In other words, move each point some distance in a random direction from its normal location. But not permanently. We&amp;rsquo;ll start with a regular grid, move each point out a ways, and then back.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;grid of points, with some randomizations&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-21/movie_04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;ol start=&#34;6&#34;&gt;&#xA;&lt;li&gt;And again, make two of these and have them rotate in opposite directions.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;rotating grids of points, with some randomizations&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-21/movie_05.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;ol start=&#34;7&#34;&gt;&#xA;&lt;li&gt;Finally, we go back to drawing the lines, and remove the points altogether, with a bit of blur to smooth it all out. And we&amp;rsquo;re back to the original&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;short line segments rotating and forming various patterns&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-21/251021.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h2 id=&#34;a-different-example&#34;&gt;A Different Example&lt;/h2&gt;&#xA;&lt;p&gt;Interestingly, the previous day I&amp;rsquo;d posted a similar piece using the same basic technique. In this case the two point sets were quite different. One was a grid exactly as I showed here, the other was just a set of random points, being moved around with 4D Simplex noise. It looks like this on its own:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;random points moving around in a changing noise field&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-21/movie_06.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;But when we apply the same technique to these two sets of points (with a max distance of 20), we get this:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;short line segments rotating and forming various patterns&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-21/251020.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;To me this looks like some kind of alien writing. The caption I gave it when I posted it was &amp;ldquo;The alien encyclopedia contains all the knowledge of the universe. If only we could read it.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;I actually like this one a lot better than the dual grid one, but the dual grid piece got more interaction and questions. I guess you never know.&lt;/p&gt;&#xA;&lt;h2 id=&#34;source&#34;&gt;Source&lt;/h2&gt;&#xA;&lt;p&gt;The source for both of these is at &lt;a href=&#34;https://codeberg.org/bit101/octanimate25&#34;&gt;https://codeberg.org/bit101/octanimate25&lt;/a&gt; if you care to dive in a bit more.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Octanimate 2025 Part I</title>
      <link>https://bit-101.com/blog/posts/2025-10-19/octanimate-2025-part-i/</link>
      <pubDate>Sun, 19 Oct 2025 12:01:02 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-10-19/octanimate-2025-part-i/</guid>
      <description>&lt;p&gt;Over the years I&amp;rsquo;ve taken part in a number of community projects. Like &amp;ldquo;7 Days of Code&amp;rdquo;, &amp;ldquo;Jamuary&amp;rdquo;, &amp;ldquo;Genuary&amp;rdquo;, etc. And a few I&amp;rsquo;ve started ad-hoc and wound up getting a decent following on - like &amp;ldquo;MayContainGifs&amp;rdquo; and &amp;ldquo;AuGIF&amp;rdquo; and something like &amp;ldquo;JulyIsForGIFs&amp;rdquo; (or was it &amp;ldquo;GulyIsForJIFS&amp;rdquo;?), where people created a gif or animation of some kind every day for a month.&lt;/p&gt;&#xA;&lt;p&gt;I was feeling the need to do this again a few weeks ago, so created &amp;ldquo;Octanimate&amp;rdquo; where I commited to posting an animation every day in October. I didn&amp;rsquo;t really do anything to promote it other than just start using the hashtag on my daily posts. I think so far one other person has used the tag. But that&amp;rsquo;s fine. It&amp;rsquo;s driven me to create and post 19 animations so far this month and I&amp;rsquo;m confident I&amp;rsquo;ll get through the next dozen days. Most of these have been on Mastodon, and you can search the tag here:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://mstdn.social/deck/tags/octanimate&#34;&gt;https://mstdn.social/deck/tags/octanimate&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;If there&amp;rsquo;s any kind of theme for this one it&amp;rsquo;s noise. Mostly just warping things smoothly with 4D Simplex noise. (See my previous post.)&lt;/p&gt;&#xA;&lt;p&gt;Also, the source for all these is up at &lt;a href=&#34;https://codeberg.org/bit101/octanimate25&#34;&gt;https://codeberg.org/bit101/octanimate25&lt;/a&gt;. It&amp;rsquo;s all in Go using my own &lt;a href=&#34;https://codeberg.org/bit101/bitlib&#34;&gt;bitlib&lt;/a&gt; and &lt;a href=&#34;https://codeberg.org/bit101/blcairo&#34;&gt;blcairo&lt;/a&gt; libraries, which are also open source, though I don&amp;rsquo;t think anyone else in the whole universe uses them. But again, fine. I love them. And anyway, if you want to figure out a technique I used on any of these, it shouldn&amp;rsquo;t be too hard. Just note the license.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, I figured I&amp;rsquo;d post the first ten days worth here. and do it again for 11-20 in the next few days, and then wrap it up at the end of the month. No comments, no explanations, just animations. Here we go:&lt;/p&gt;&#xA;&lt;p&gt;Day 1:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251001.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 2:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251002.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 3:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251003.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 4:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251004.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 5:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251005.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 6:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251006.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 7:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251007.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 8:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251008.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 9:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251009.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;Day 10:&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-19/251010.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Looping Noise</title>
      <link>https://bit-101.com/blog/posts/2025-09-28/looping-noise/</link>
      <pubDate>Fri, 17 Oct 2025 09:00:00 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-09-28/looping-noise/</guid>
      <description>&lt;p&gt;This is a story. A story of laziness and stupidity. But also a story of redemption and victory.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s about noise. Like Perlin noise or Simplex noise. And making it move. But making it move in a specific way. It&amp;rsquo;s about the problem, the crappy solution I lived with for too long, the eventual hack I figured out, and finally, the real solution which I knew was there all along but for some reason never implemented. And some other roads worth following in the future.&lt;/p&gt;&#xA;&lt;h2 id=&#34;setting-the-stage&#34;&gt;Setting the stage&lt;/h2&gt;&#xA;&lt;p&gt;So you want to animate some noise. It doesn&amp;rsquo;t really matter what you&amp;rsquo;re doing with that noise or how you&amp;rsquo;re rendering it. For something to work with, let&amp;rsquo;s just render some noise on a square surface. We&amp;rsquo;ll get the value of the noise at each pixel location and convert that to a grayscale value and color the pixel using that. I&amp;rsquo;m gonna go with some pseudocode in this post because I don&amp;rsquo;t want to get caught up in language details.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt; to width {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt; to height {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise2(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;map&lt;/span&gt;(n, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setPixelGray(x, y, n)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Generally you want to scale your pixel values down a lot lower before feeding them into your noise function. Very low values give smoothly changing values, while high values look more chaotic. The actual scaling values you need will differ based on the noise algorithm you are using, but you&amp;rsquo;ll eventually get a feel for the ranges that work.&lt;/p&gt;&#xA;&lt;p&gt;The pseudocode &lt;code&gt;noise2&lt;/code&gt; function is 2d noise - generally Perlin or Simplex.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m assuming your noise functions return values from -1 to 1, and you have a function that will set a pixel to a grayscale value from 0 to 1, so the &lt;code&gt;map&lt;/code&gt; function converts between the two. If you want to learn more about &lt;code&gt;map&lt;/code&gt;, &lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-26/fotd-map/&#34;&gt;read this&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Again, your language and platform and libraries may do something different, but I trust you can work it out. A possible result from the above:&lt;/p&gt;&#xA;&lt;img alt=&#34;2d simplex noise in grayscale&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-05/image_01.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;animating-it&#34;&gt;Animating it&lt;/h2&gt;&#xA;&lt;p&gt;OK, now we want to make it move!&lt;/p&gt;&#xA;&lt;p&gt;We could move it on the x-axis or the y-axis like so&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise2((x &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; offset) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#555&#34;&gt;//&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;or&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise2(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, (y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; offset) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, I&amp;rsquo;m considering that &lt;code&gt;offset&lt;/code&gt; is some value that is changing over the course of the animation. Maybe the first frame it&amp;rsquo;s 0 and on the last frame it&amp;rsquo;s 400. This is going to give you something like this:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animated 2d simplex noise in grayscale animating on x axis&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-05/movie_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Not particularly exciting. Yes, we are animating the noise, but we&amp;rsquo;re not really changing what the noise looks like, we&amp;rsquo;re just panning across a noise field. Also, when it gets to the end it jumps back to the beginning. Nothing more I hate than a non-looping short animation. Well, there a few things I hate more, but non-looping animations are up there.&lt;/p&gt;&#xA;&lt;h2 id=&#34;into-higher-dimensions&#34;&gt;Into higher dimensions!&lt;/h2&gt;&#xA;&lt;p&gt;OK, not &lt;em&gt;higher&lt;/em&gt; higher dimensions, but higher than two. Three for now.&lt;/p&gt;&#xA;&lt;p&gt;If we use three-dimensional noise, rather than a flat plane of noise, we have a solid space of noise. It&amp;rsquo;s infinite in size, but you can imagine it as a cube. We can move through this cube on the x, y or z axes. At any given point, we&amp;rsquo;ll be looking at one slice of the noise at a given z value. It&amp;rsquo;s a bit like doing a CT scan or MRI or ultrasound on that noise cube. And if you change the z value over time, you can animate those slices and the noise morphs around into different shapes.&lt;/p&gt;&#xA;&lt;p&gt;So we&amp;rsquo;re looking at something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise3(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, z)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We could scale &lt;code&gt;z&lt;/code&gt; here too, but in this case we&amp;rsquo;re not stuck with pixel coordinates, so there&amp;rsquo;s no real need to do that. We can just have &lt;code&gt;z&lt;/code&gt; interpolate between whatever values we want. For example, &lt;code&gt;z&lt;/code&gt; might be 0.0 on the first frame, and 1.0 on the last frame.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s what this looks like:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animated 2d simplex noise in grayscale animating on z axis&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-05/movie_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;OK, that&amp;rsquo;s pretty cool, but it certainly doesn&amp;rsquo;t loop. It goes from one z value to another and when the animation ends it jumps back to the start and plays again. So we need a smooth transition back around to the starting point. (If you&amp;rsquo;re doing a real-time animation where you can just continuously increase &lt;code&gt;z&lt;/code&gt; then you&amp;rsquo;re all set. But if you&amp;rsquo;re doing a rendered animation, you&amp;rsquo;ll want to figure out how to end where you started.)&lt;/p&gt;&#xA;&lt;p&gt;So here&amp;rsquo;s the crappy solution I&amp;rsquo;ve been using for years. [shame&amp;hellip;]&lt;/p&gt;&#xA;&lt;h2 id=&#34;diligence-overcomes-difficulties-sloth-makes-them---ben-franklin&#34;&gt;Diligence overcomes difficulties; sloth makes them - Ben Franklin&lt;/h2&gt;&#xA;&lt;p&gt;I animate the z value from value A to value B (say 0 to 1) for the first half of the animation, and then back to 0 again for the second half. If we do this linearly, it looks like this:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animated 2d simplex noise in grayscale animating on z axis&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-05/movie_04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;A bit better. It loops, but not very smoothly. It moves along at a linear pace then hits the end and bounces back the other way. So linear is not good - and not what I actually used. Just showing the process step by step.&lt;/p&gt;&#xA;&lt;p&gt;Imagining you have a normalized value &lt;code&gt;t&lt;/code&gt; that is 0.0 on the first frame and 1.0 on the last one (technically just shy of 1.0 but that&amp;rsquo;s a story for another day). You create your noise like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise3(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, sin(t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; tau) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; offset)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Multipling &lt;code&gt;t&lt;/code&gt; by &lt;code&gt;tau&lt;/code&gt; (2PI) gives us an angle. Take the sine of that and multiply it by an offset value - how far we want it to move back and forth in the z-axis. 1.0 will do fine for starters.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animated 2d simplex noise in grayscale animating on z axis&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-05/movie_05.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This is pretty nice&amp;hellip; way better than the linear version. No jarring jumps at least. It kind of coasts slowly to a stop before reversing. I did things like this for years. And at some point I started hating it.&lt;/p&gt;&#xA;&lt;p&gt;What I want is something that just continues to morph the noise&amp;rsquo;s shape, but magically ends up exactly back where it started, without reversing.&lt;/p&gt;&#xA;&lt;p&gt;The problem is we&amp;rsquo;re just using three dimensions, taking 2D slices, sliding down through the cube and back up. So the reverse path is exactly the same as the forward path. We need another dimension to get more movement. Then you could loop through those two dimensions in a circle or ellipse and have a unique path that arrives back where it started.&lt;/p&gt;&#xA;&lt;h2 id=&#34;id-like-to-borrow-a-dimension-please&#34;&gt;I&amp;rsquo;d like to borrow a dimension please&lt;/h2&gt;&#xA;&lt;p&gt;At first I thought, &amp;ldquo;Oh, I can just borrow the x or y dimension and make it do double duty. And you kind of can. The code would be something like this&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n &lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt; noise3(x, y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cos(t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; tau) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; offset, sin(t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; tau) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; offset)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we pre-multiply &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; by &lt;code&gt;scale&lt;/code&gt; only to make the longer line a bit shorter.&lt;/p&gt;&#xA;&lt;p&gt;Again, we have &lt;code&gt;t * tau&lt;/code&gt; to get an angle, but now we take take the cosine and sine of that, times an offset. That gives us a unique y and z value that will follow a circular path and end up back at the start. Sounds good, but here&amp;rsquo;s what it looks like:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animated 2d simplex noise in grayscale animating on y and z axes&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-05/movie_06.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;It&amp;rsquo;s not super satisfying. It&amp;rsquo;s a bit of the original panning combined with the 3D slice scanning. It&amp;rsquo;s still got that move through to a certain point, then reverse feel to it. Not as bad, but not great. So borrowing a dimension is not going to work.&lt;/p&gt;&#xA;&lt;p&gt;So a minute ago I said we need another dimension. That right there is the solution. We just need 4D noise. It exists. But I didn&amp;rsquo;t have it in my library. And I didn&amp;rsquo;t want to dig up a version of it and port it to my language of choice (Go). So rather than do that&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-hack&#34;&gt;The Hack&lt;/h2&gt;&#xA;&lt;p&gt;Learning nothing, I steadfastly tried another way to get around 4D noise. And I&amp;rsquo;m kind of ashamed to say, it worked out pretty damn well.&lt;/p&gt;&#xA;&lt;p&gt;My thoughts: What if I take the z-slices in one area of the noise field using sine, and then take z-slices from another area using cosine? Then average the two of them. Since the movement is out of phase, one would be moving down quickly while the other was slowing down, and the first would be still moving down while the other was moving back up.&lt;/p&gt;&#xA;&lt;p&gt;Or, another way of saying it is: rather than adding a dimension or borrowing one, we&amp;rsquo;re just using a different part of the noise field and pretending it&amp;rsquo;s another dimension. But this gives us two different slices, which we just average together.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n1 &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise3(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, sin(t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; tau) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; offset)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n2 &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise3(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;, y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;, cos(t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; tau) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; offset)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; (n1 &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; n2) &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here&amp;rsquo;s what that looks like:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animated 2d simplex noise smooth&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-05/movie_07.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Honestly, that&amp;rsquo;s not too bad. Animation-wise, it&amp;rsquo;s exactly what I wanted. I did find that the averaging wound up kind of washing things out and reducing contrast. In other words, averaging is going to result in somewhat of a bell curve. Fewer darks, fewer lights, more grays. So I used another small hack after the above to increase the contrast:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;map&lt;/span&gt;(n, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.8&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0.8&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1.0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Due to the averaging, more values will wind up in between -0.8 and 0.8. So we just map value in that range to 0, 1 in the &lt;code&gt;map&lt;/code&gt; function we were already using. It makes the darks darker and the lights lighter. You can play around with that value to get different levels of contrast. But beware, too much and your darks and whites get overblown.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a corrected version:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animated 2d simplex noise smooth&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-05/movie_08.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I was pretty proud of this. And also rather ashamed at the same time. If you&amp;rsquo;re on a platform where you just can&amp;rsquo;t get a 4D noise function, I think this might be a viable solution. But it was time to put on my big boy boots and do it right.&lt;/p&gt;&#xA;&lt;h2 id=&#34;just-do-it&#34;&gt;Just do it&lt;/h2&gt;&#xA;&lt;p&gt;Yup. I ported a Simplex 4D function to Go.&lt;/p&gt;&#xA;&lt;p&gt;It wasn&amp;rsquo;t all that hard. I should have done it years ago. I actually re-ported all four dimensions from &lt;a href=&#34;https://github.com/kev009/craftd/tree/master/plugins/survival/mapgen/noise&#34;&gt;this C code&lt;/a&gt; into Go. All told it took about an hour. And I&amp;rsquo;ve been loving 4D noise for weeks since.&lt;/p&gt;&#xA;&lt;p&gt;To make smoothly looping 2D noise using 4D noise, you use two of the dimensions for your rendering and the other two to loop in a &amp;ldquo;circle&amp;rdquo;. Something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise4(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; scale, cos(t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; tau) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; offset, sin(t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; tau) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; offset)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m using the x and y dimesions for my x and y values and doing the looping with z and w.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s an example of rendering that:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animated 4d simplex noise&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-05/movie_09.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h2 id=&#34;resources&#34;&gt;Resources&lt;/h2&gt;&#xA;&lt;p&gt;I did a lot of research on this while I was trying to figure out a way to do this without using 4D noise. There may still be a valid way to do it, but I didn&amp;rsquo;t find one using regular noise functions. A lot of Reddit and StackOverflow answers merely talk about smoothly animating 1D noise using 2D noise. But they rarely express it in those terms, just like, &amp;ldquo;Yeah, it&amp;rsquo;s easy to have looping noise, just use 2D noise with polar coordinates and that will smoothly loop the resulting value.&amp;rdquo; Yes, the 1D value.&lt;/p&gt;&#xA;&lt;p&gt;Daniel Shiffman has a nice video on this topic, where he explains more in depth about, again, looping 1D noise using 2D. He gets into &lt;em&gt;animating&lt;/em&gt; 2D noise using 3D noise, but never gets to the point of looping the 2D noise. Still, it&amp;rsquo;s a very well explained video, which might clarify some of the points I brushed over here.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ZI1dmHv3MeM&#34;&gt;Polar Perlin Noise Loops&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;He also mentions Etienne Jacobs&amp;rsquo; post on looping noise:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://necessarydisorder.wordpress.com/2017/11/15/drawing-from-noise-and-then-making-animated-loopy-gifs-from-there/&#34;&gt;Drawing from noise&amp;hellip;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This goes into many of the same things and gets into 4D noise, but in my mind doesn&amp;rsquo;t go too much into the &amp;ldquo;why&amp;rdquo; of needing four dimensions.&lt;/p&gt;&#xA;&lt;p&gt;And finally, I just found this Shiffman video well after I wrote this post and as I was just tidying up the resources section:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=3_0Ax95jIrk&amp;amp;t=40s&#34;&gt;4D Open Simplex Noise Loop&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;one-last-thing&#34;&gt;One last thing&amp;hellip;&lt;/h2&gt;&#xA;&lt;p&gt;There is another way around this problem. It has to do with tiling noise. There are ways to make Perlin and Simplex noise tileable in different dimensions. This is usually used to make textures that &amp;hellip; well, tile. In other words, for a 2D noise tile, the left edge of the tile would perfectly match up and smoothly blend to the right edge. Same with top and bottom. But if you imagine sampling values going from left to right in a stright line, you&amp;rsquo;d wind up with the same values over and over. Essentially the same as if you had looped it. (Actually the same thing would happen if you went in a straight line in &lt;em&gt;any&lt;/em&gt; direction!)&lt;/p&gt;&#xA;&lt;p&gt;If you extend that to 3D tiled noise, you could create looping noise by just moving through the 3D slices of that noise field, without any need for the fourth dimension.&lt;/p&gt;&#xA;&lt;p&gt;However, you need a noise algorithm that creates tiling noise in order to do this. Here&amp;rsquo;s a paper on that, if you want to explore this route. &lt;a href=&#34;https://jcgt.org/published/0011/01/02/paper.pdf&#34;&gt;Tiling Simplex Noise and Flow Noise&#xA;in Two and Three Dimens&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;In short:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I knew that 4D noise was the solution very early on, but was lazy about porting 4D noise into my library.&lt;/li&gt;&#xA;&lt;li&gt;I assumed there was some other trick people were using to get around using 4D noise.&lt;/li&gt;&#xA;&lt;li&gt;I tried a bunch of things that were not great.&lt;/li&gt;&#xA;&lt;li&gt;Many of the Reddit and Stack Overflow threads on this seem to say it&amp;rsquo;s easy, but they are describing an easier problem, not the probem I was trying to solve.&lt;/li&gt;&#xA;&lt;li&gt;I eventually did hack together a workable solution!&lt;/li&gt;&#xA;&lt;li&gt;Porting 4D Simplex noise to Go was pretty easy.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Proximity Webs</title>
      <link>https://bit-101.com/blog/posts/2025-10-12/proximity-webs/</link>
      <pubDate>Sun, 12 Oct 2025 14:30:08 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-10-12/proximity-webs/</guid>
      <description>&lt;p&gt;I don&amp;rsquo;t think I ever actually wrote about this before, but I decided I probably should. It&amp;rsquo;s about a technique I call a &amp;ldquo;proximity web&amp;rdquo;. It&amp;rsquo;s something I first did about 23 years ago. I don&amp;rsquo;t recall seeing anything quite like it before, but I&amp;rsquo;ve seen it hundreds of times since then. I&amp;rsquo;ve seen it on posters, billboards, tv shows, movies, many web sites, music videos, etc.&lt;/p&gt;&#xA;&lt;p&gt;Now I&amp;rsquo;m not going to be so bold as to claim that I invented this. But&amp;hellip; I might have??? There is an old friend of mine who has since become pretty big in the generative art world. They once told me that as far as they were concerned, I invented it.&lt;/p&gt;&#xA;&lt;p&gt;I can say that I came up with the technique on my own. But whether people copied me and eventually it became a thing, or whether someone else did it independently before or after me and others copied that person, I have no idea. All I know is that whenever I see it somewhere, I grin a little bit and internally take a bit of ownership.&lt;/p&gt;&#xA;&lt;p&gt;For the record, here is a recording of a Flash movie I published on September 20, 2002:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-12/proximity_01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This was the first time I used the technique. I did a bunch more similar things over the next week or so. Here are just a couple of examples:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-12/proximity_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-12/proximity_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I did a lot more of this over the next few years, but that&amp;rsquo;s the basic idea.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a quick example I just put together now while writing this. This is the kind of thing you&amp;rsquo;re most likely to see out in the wild. Sorry it doesn&amp;rsquo;t loop. I got lazy. But this is very much what I was posting in 2002 and 2003 and beyond.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-10-12/proximity_04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Ignore the mechanics going on in those examples in terms of how things are moving around. The proximity web concept is just how they are connected. In short:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;There is a maximum connection distance between any two nodes.&lt;/li&gt;&#xA;&lt;li&gt;If the distance between two nodes is more than that, there is no connection.&lt;/li&gt;&#xA;&lt;li&gt;If the distance is at or below that threshold, the nodes will be connected with a line.&lt;/li&gt;&#xA;&lt;li&gt;The thickness of that line is inversely proportional to the distance between the nodes.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;In other words, when the nodes are very close together, the line is very thick/bright/whatever. As the distance increases towards the maximum, the line gets thinner and lighter so that when it reaches the maximum it becomes zero width.&lt;/p&gt;&#xA;&lt;p&gt;In pseudocode&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dist &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; distance(nodeA, nodeB)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; dist &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; maxDist {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  setLineWidth(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; dist &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; maxDist)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  strokeLine(nodeA, nodeB)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this case the thickness goes from 1.0 when the nodes are right on top of each other, to 0.0 when they are at &lt;code&gt;maxDist&lt;/code&gt; away. But it&amp;rsquo;s simple math to change that to whatever range you want, or use it to define brightness instead of width or whatever else.&lt;/p&gt;&#xA;&lt;p&gt;This is a really cool effect when animated because it&amp;rsquo;s like the connection is stretching as the nodes move apart, and finally breaks when it gets too thin.&lt;/p&gt;&#xA;&lt;p&gt;Note well, I&amp;rsquo;m NOT saying I thought up the idea of connecting dots with lines. But this particular rendering effect where the nodes are floating around and the lines fade in and out based on proximity is rather unique.&lt;/p&gt;&#xA;&lt;p&gt;Jump forward a few years to October 7, 2008. I posted something on my Art from Code site, called &lt;a href=&#34;https://archive.artfromcode.com/?p=657&#34;&gt;Lissajous Webs&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This used a Lissajous curve to create a large number of static points and connected them all with a proximity web. The huge number of connections made some really interesting textures.&lt;/p&gt;&#xA;&lt;p&gt;Here are a few examples:&lt;/p&gt;&#xA;&lt;img alt=&#34;lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/web_02.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/web_04.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/web_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The next day I threw some randomness into the Lissajous function and came up with &lt;a href=&#34;https://archive.artfromcode.com/?p=666&#34;&gt;Random Lissajous Webs&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Again some examples.&lt;/p&gt;&#xA;&lt;img alt=&#34;randomized lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/randomweb_02.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;randomized lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/randomweb_04.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;randomized lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/randomweb_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;These evoked a really mysterious look, which I loved to death. And others did too. In fact, if you search the term &amp;ldquo;Random Lissajous Web&amp;rdquo; you&amp;rsquo;ll find a few examples of this exact thing with, sadly, rarely any attribution. Even when the authors are using a license that requires others to attribute &lt;em&gt;their&lt;/em&gt; work, they don&amp;rsquo;t attribute the technique they used - or even the name! Sigh. Some examples from as late as 2023.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://openprocessing.org/sketch/1836813&#34;&gt;https://openprocessing.org/sketch/1836813&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://openprocessing.org/sketch/1793237&#34;&gt;https://openprocessing.org/sketch/1793237&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;On the plus side, I did get some attribution in the massive generative art book, &lt;a href=&#34;https://amzn.to/3VYBoKy&#34;&gt;Generative Design&lt;/a&gt;. When I say &amp;ldquo;massive&amp;rdquo; I mean it&amp;rsquo;s about two and a half inches thick and chock full of amazing work, examples and code. (Note, this was from an era before the word &amp;ldquo;generative&amp;rdquo; had been stolen by AI &amp;ldquo;creators&amp;rdquo;.)&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a whole chapter with webs based on my work. In fact they got my permission for this beforehand and I even got a free copy of the book, which was nice.&lt;/p&gt;&#xA;&lt;img alt=&#34;lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/genweb_01.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/genweb_02.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;lissajous points connected by lines&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-10-12/genweb_03.jpg&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not complaining or looking for any big pats on the back. I&amp;rsquo;m not suing anyone even if I could. I&amp;rsquo;m not accusing anyone of ripping me off maliciously. I put my stuff out there as open source, often without a lot of thoughts on licensing. And a lot of people liked it and used it for a lot of things. I&amp;rsquo;m honored. All I&amp;rsquo;m asking is that if you see something like this out there in the world&amp;hellip; give me a virtual fist bump, at least in your own mind!&lt;/p&gt;&#xA;&lt;p&gt;And of course, if you do have some examples of this that predate 2002, I&amp;rsquo;d love to see them. It would crush my ego, but there&amp;rsquo;s not much of that left anyway.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Dictionaries</title>
      <link>https://bit-101.com/blog/posts/2025-09-27/dictionaries/</link>
      <pubDate>Sat, 27 Sep 2025 08:11:47 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-09-27/dictionaries/</guid>
      <description>&lt;p&gt;Growing up, and even into my 20s and 30s I was an avid dictionary user. I was taught how to use them and I always had one or more on hand. Nice thick volumes that had the spelling, the pronunciation, all the different definitions broken into parts of speech, including transitive and intransitive verbs, example usages, and etymology. Maybe even some synonyms or special usage notes if there was something tricky about it. I especially always dug into the etymology. Knowing the history of a word really got that word to sink in for me. And having done that a lot made it easy to understand other words with similar backgrounds.&lt;/p&gt;&#xA;&lt;p&gt;I haven&amp;rsquo;t had an actual dictionary for years. But I still try to look up words using the web. It&amp;rsquo;s just not the same. I didn&amp;rsquo;t think about it too much, but it finally just got frustrating enough where I snapped. Usually I just type the word, plus &amp;ldquo;definition&amp;rdquo;. That usually gives me some kind of search engine definition and links to random dictionary sites. I generally try to go into one of the dictionary sites.&lt;/p&gt;&#xA;&lt;p&gt;Let me tell you&amp;hellip; dictionary sites are horrible.&lt;/p&gt;&#xA;&lt;h2 id=&#34;dictionary-sites&#34;&gt;Dictionary Sites&lt;/h2&gt;&#xA;&lt;p&gt;They&amp;rsquo;re even generally run by companies that are well known for publishing quality dictionaries. But once they get on the web, it&amp;rsquo;s no longer a dictionary. It&amp;rsquo;s a web site. It&amp;rsquo;s an experience. It&amp;rsquo;s a social network. It&amp;rsquo;s a marketing opportunity. You get ads and games and quizzes and trending words, slang guides to the new words kids are using. Word of the day. How-tos, interesting word histories, videos, podcasts, shops, merch, mailing lists. I&amp;rsquo;m not making this up, I&amp;rsquo;m just cataloging what I see on the Merriam Webster front page.&lt;/p&gt;&#xA;&lt;img alt=&#34;front page of merriam-webster.com&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;See that tiny red bar at the top? That&amp;rsquo;s the part I&amp;rsquo;m interested in - where you can enter a word to find its definition.&lt;/p&gt;&#xA;&lt;p&gt;See all the rest of that crap? Not interested in any of it.&lt;/p&gt;&#xA;&lt;p&gt;It should just look like the Google home page. Logo, search field. End of story.&lt;/p&gt;&#xA;&lt;p&gt;Now, that&amp;rsquo;s Merriam Webster, which is a solid dictionary. But OMG, the site&amp;hellip; makes me cry. A computer and a web site should be the best thing that ever happened to a dictionary. No flipping pages back and forth. Just type the word and you&amp;rsquo;re there. Don&amp;rsquo;t understand a word in the definition? Click to go to that, then click back. Why did they have to ruin it by doing all this other stuff?&lt;/p&gt;&#xA;&lt;p&gt;But OK, what happens when you look up a word? Let&amp;rsquo;s look up &amp;ldquo;canonical&amp;rdquo; - for no reason. It just came to mind. No, it&amp;rsquo;s not a reference to the company that puts out Ubuntu. Just a random word.&lt;/p&gt;&#xA;&lt;img alt=&#34;web page for definition of canonical&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Alright. We have a nav menu with games, word of the day, grammar, word finder, slang, newsletters, wordplay, and more. And a sidebar nav of what&amp;rsquo;s on this page. And a couple of call to actions for games and stuff.&lt;/p&gt;&#xA;&lt;p&gt;But we do have definitions. And each definition has an example.&lt;/p&gt;&#xA;&lt;p&gt;And we have more examples, but these are &amp;ldquo;automatically compiled from online sources to show current usage&amp;rdquo;. I don&amp;rsquo;t really like this idea. Real dictionaries hand curate useful examples, not do automated web searches. At least it&amp;rsquo;s not obviously &amp;ldquo;AI&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;Then we have etymology and some other stuff&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;web page for definition of canonical&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And more stuff&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;web page for definition of canonical&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I don&amp;rsquo;t need rhymes and nearby words or citations. This isn&amp;rsquo;t bad stuff, but it&amp;rsquo;s just trying to cram everything possible in here.&lt;/p&gt;&#xA;&lt;p&gt;Keep scrolling and you&amp;rsquo;ll be back into games and merch and stuff.&lt;/p&gt;&#xA;&lt;p&gt;The etymology entry links to the root word, &amp;ldquo;canon&amp;rdquo;, which is fine because it&amp;rsquo;s a click away. An actual dictionary would do the same thing.&lt;/p&gt;&#xA;&lt;img alt=&#34;web page for etymology of canon&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;So honestly, this is decent content. It&amp;rsquo;s just surrounded by a deafening amount of random crap.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not going to compare other sites. Because this is one of the better ones - at least in terms of content.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-about-apps&#34;&gt;What about apps?&lt;/h2&gt;&#xA;&lt;p&gt;My next thought was to try an app. Honestly, I was about to go buy a physical dictionary, but hoped an app might be better than the sites I&amp;rsquo;d seen.&lt;/p&gt;&#xA;&lt;p&gt;The two I tried were the Merriam Webster app and the Oxford Dictionary app. Both on Android. Both have free versions with ads and subscription models. Ugh. But I&amp;rsquo;ll keep an open mind.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-oxford-dictionary-app&#34;&gt;The Oxford Dictionary app&lt;/h3&gt;&#xA;&lt;p&gt;The Oxford Dictionary app contains something like fifteen different dictionaries - English, American and a bunch of other languages. It also has full page ads that you have to wait like 10-15 seconds before you can see the content. Every time you look up a word. Like on those really, really crappy free games. So, so, so bad. But, you can get rid of those for just $2.99 per month. $36 a year for a dictionary? OK, fifteen dictionaries, thirteen of which I&amp;rsquo;ll never touch. Meh.&lt;/p&gt;&#xA;&lt;p&gt;While the content was decent, the format, layout and overall UX just wasn&amp;rsquo;t great.&lt;/p&gt;&#xA;&lt;p&gt;Interestingly, I also checked out the American Heritage Dictionary app. It&amp;rsquo;s by the same publisher and looks exactly the same as the Oxford app. Only has the one dictionary and it&amp;rsquo;s $11.99 per year. Or you can buy it for $49.99.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-merriam-webster-app&#34;&gt;The Merriam Webster app&lt;/h3&gt;&#xA;&lt;p&gt;Alright, we&amp;rsquo;re getting somewhere. Again, ad supported free app, but it&amp;rsquo;s a small banner at the bottom of the page. Not awful. And it&amp;rsquo;s $2.99 per year to go ad free. Also, $5.99 per year for some kind of pro version, which also gives you a kids dictionary, and medical and legal dictionary. Not sure if I needed those, but I went for the $5.99 version. I think it might have some other features. But that&amp;rsquo;s a decent price no matter what. The physical version of this same dictionary would be around $21. And would not fit in my pocket.&lt;/p&gt;&#xA;&lt;p&gt;So what&amp;rsquo;s it look like? Any better than the web site?&lt;/p&gt;&#xA;&lt;p&gt;Yes! I was very pleasantly surprised!&lt;/p&gt;&#xA;&lt;p&gt;When you first open it, you get this:&lt;/p&gt;&#xA;&lt;img alt=&#34;home page of merriam-webster app&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Not TOO horrible. Only two random distractions.&lt;/p&gt;&#xA;&lt;p&gt;Once you&amp;rsquo;re in the word though, it&amp;rsquo;s great.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s got the same defintions and embedded examples as the site and some additional info, including etymology. But that&amp;rsquo;s it. Nicely formatted and readable.&lt;/p&gt;&#xA;&lt;img alt=&#34;app page for canonical&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Extra info, but not too much.&lt;/p&gt;&#xA;&lt;img alt=&#34;app page for canonical&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Other nice features&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Audible pronunciation&lt;/li&gt;&#xA;&lt;li&gt;Thesaurus too! That&amp;rsquo;s always useful, practical, applicable, functional.&lt;/li&gt;&#xA;&lt;li&gt;Click on any word on the page to look up that word.&lt;/li&gt;&#xA;&lt;li&gt;Scroll left/right to go through your history. I actually missed that at first and was trying to back navigate, which just took me to the home page, which was very annoying.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For words that are in the kids, medical, and/or legal dictionaries, you get links there as well.&lt;/p&gt;&#xA;&lt;img alt=&#34;app page for form&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_09.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;To be honest, the kids dictionary is pretty good if you just want a quick idea of what something means.&lt;/p&gt;&#xA;&lt;p&gt;The app menu also lets you see a list of recent words you looked up, as well as any words you&amp;rsquo;ve saved. You can also share definitions&amp;hellip; oki doki, whatever.&lt;/p&gt;&#xA;&lt;p&gt;Overall, this is a winner. Just what I wanted.&lt;/p&gt;&#xA;&lt;h2 id=&#34;other-options&#34;&gt;Other options&lt;/h2&gt;&#xA;&lt;h3 id=&#34;desktop-app&#34;&gt;Desktop app&lt;/h3&gt;&#xA;&lt;p&gt;I thought of trying Stardict. This is an open source dictionary app, which you can add different dictionaries to (there are a lot). On my ebook readers (Onyx Boox devices) I use KOReader which integrates with Stardict compatible dictionaries. It&amp;rsquo;s a great concept, but the content varies widely, depending on which dictionaries you add to it. Many of them are outdated and oddly formatted. But it&amp;rsquo;s great to have it integrated in the ebook reader. Just tap a word and get a definition. But all too often, there&amp;rsquo;s no result found in any of the three dictionaries I have installed there.&lt;/p&gt;&#xA;&lt;p&gt;At any rate, a couple of attempts to install Stardict on my computer just led to segfaults. I&amp;rsquo;m sure I could debug it, but I&amp;rsquo;m pretty happy with the Merriam Webster app for now.&lt;/p&gt;&#xA;&lt;h3 id=&#34;wiktionary&#34;&gt;Wiktionary&lt;/h3&gt;&#xA;&lt;p&gt;It was also suggested trying Wiktionary. This is a free dictionary operated by Wikimedia, who also operate Wikipedia, etc. This is also very good, but goes a bit deep. Oddly, etymology is up front, followed by usually several different pronunciation guides before getting to an extensive list of definitions, often very specialized and obscure ones - politics, engineering, medical, economics, finance, programming, philosophy, etc. Plus derived terms, related terms, translations, references, etc.&lt;/p&gt;&#xA;&lt;img alt=&#34;wiktionary definition of canonical&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-27/dictionary_10.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Definitely errs on the side of being complete, but a bit overwhelming sometimes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;Just my thoughts on dictionaries and how good they can be and how bad some have gotten by going on line, which should make them even better. Find whatever works for you.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Migrating away from Github</title>
      <link>https://bit-101.com/blog/posts/2025-09-07/github/</link>
      <pubDate>Sun, 07 Sep 2025 06:00:00 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-09-07/github/</guid>
      <description>&lt;p&gt;I am no longer relying on github for code hosting, version control, pages, anything else. I&amp;rsquo;ve fully moved over to &lt;a href=&#34;https://codeberg.org/bit101&#34;&gt;Codeberg&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not going to go into the reasons why I moved. Not here at least. If you are interested, I did a write up in my last remaining repository at github: &lt;a href=&#34;https://github.com/bit101/farewell&#34;&gt;https://github.com/bit101/farewell&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;TLDR, it&amp;rsquo;s the whole push on AI there. Multiple angles, and I&amp;rsquo;m just not in alignment with where they are headed. Codeberg seems nice.&lt;/p&gt;&#xA;&lt;p&gt;This post is more to let people know where my stuff is, and to give a bit of a story about how I made the move.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-moving-process&#34;&gt;The Moving Process&lt;/h2&gt;&#xA;&lt;p&gt;When I decided to move, I had something like 123 repos on github. So, no, it was not a decision I made lightly. It took me weeks to move everything over one by one. Codeberg is great though. they have a migration function that lets you pull in your code from a bunch of different places.&lt;/p&gt;&#xA;&lt;img alt=&#34;migration ui on codeberg&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-07/migration.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You choose github (or whatever service you are coming from) and fill in the details.&lt;/p&gt;&#xA;&lt;img alt=&#34;github migration ui on codeberg&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-09-07/migration2.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You&amp;rsquo;ll need to generate an access token on github and use it here. Tip: make sure you specify read access on just about everything when making the token so that Codeberg will be able to grab what it needs.&lt;/p&gt;&#xA;&lt;p&gt;BTW, don&amp;rsquo;t forget to set up your ssh key on Codeberg. I&amp;rsquo;m going to assume you know how to do that. No different than github or anywhere else. Just figured I&amp;rsquo;d say it so nobody accuses me of skipping such an important step.&lt;/p&gt;&#xA;&lt;p&gt;Another tip: get the http address of the github repo, not the ssh address. If you grab the wrong one, it will tell you though. I did it a bunch of times.&lt;/p&gt;&#xA;&lt;p&gt;And you can just migrate the data (all the commits) or stuff like pull requests, releases, labels, issues, etc. If all goes well, which it generally does, your repo is now on Codeberg.&lt;/p&gt;&#xA;&lt;p&gt;Then you can go into github and archive or delete it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;updating-your-local-clone&#34;&gt;Updating your local clone&lt;/h3&gt;&#xA;&lt;p&gt;Now, assuming you had the repo cloned on a local machine, you&amp;rsquo;ll need to update that so it pushes and pulls to Codeberg now.&lt;/p&gt;&#xA;&lt;p&gt;Go into the root folder of the repo and type:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git remote set-url origin &amp;lt;codeberg repo address&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You get that url from Codeberg, the same as if you were going to clone it. Something like &lt;code&gt;ssh://git@codeberg.org/bit101/mymigratedcode.git&lt;/code&gt;. Here, you&amp;rsquo;ll almost certainly want the ssh address, unless it&amp;rsquo;s just read only.&lt;/p&gt;&#xA;&lt;p&gt;Now you should be able to type &lt;code&gt;git remote show origin&lt;/code&gt; and see that the url is updated. And of course pull, push, etc.&lt;/p&gt;&#xA;&lt;h3 id=&#34;stuff-that-didnt-make-it&#34;&gt;Stuff that didn&amp;rsquo;t make it&lt;/h3&gt;&#xA;&lt;p&gt;There were a lot of repos that were just old code that I know I&amp;rsquo;ll never touch again, projects I started that I never continued, libraries that probably won&amp;rsquo;t work with any modern code, early versions of projects that were more fully fleshed out in a later attempt. Most of that just got deleted. There were also a bunch of JavaScript libraries that probably had some interesting things in them, even though I&amp;rsquo;m not actively using them. I combined all of them into one repo and migrated that. In the end I wound up with 60-something repos. So things got cut almost in half. No regrets at all on any of the stuff that went away.&lt;/p&gt;&#xA;&lt;h2 id=&#34;go-code&#34;&gt;Go Code&lt;/h2&gt;&#xA;&lt;p&gt;You may not run into this, but a lot of my work over the past eight years has been in Go, which has it&amp;rsquo;s own dependency managment system that&amp;rsquo;s based on where the code lives - generally a git repo. So, for example, if I wanted to pull in my &lt;code&gt;bitlib&lt;/code&gt; library into a project, I&amp;rsquo;d type &lt;code&gt;go get github.com/bit101/bitlib&lt;/code&gt;. Within that library is a &lt;code&gt;go.mod&lt;/code&gt; file that will look like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;module&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;github.com&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bit101&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bitlib&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;go&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1.22.1&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And there will be another &lt;code&gt;go.sum&lt;/code&gt; file that is generated when you actually meet the dependencies by pulling in a specific version of a library.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;github.com&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bit101&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bitlib&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;v0&lt;span style=&#34;color:#f60&#34;&gt;.4.7&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;h1:&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;H&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;UNQmEMPkNk2CIih0hVWnL1GP4lEH9BnZHupZ&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;Pns=&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;github.com&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bit101&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bitlib&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;v0&lt;span style=&#34;color:#f60&#34;&gt;.4.7&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;go&lt;/span&gt;.mod&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;h1:d0L7f0E8a21VD7cDuVuIsNyD4yqqxZsvgKxh5Nx433E=&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, each file that imports anything from &lt;code&gt;bitlib&lt;/code&gt; will have stuff like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;import&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/bitlib/geom&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/bitlib/random&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So&amp;hellip; for each library I ported over, I had to go through all the files and replace &lt;code&gt;github.com/bit101/...&lt;/code&gt; with &lt;code&gt;codeberg.org/bit101/...&lt;/code&gt;. Then, for each project I ported over I had to do the same. Actually, I generally just deleted the &lt;code&gt;go.sum&lt;/code&gt; file and ran &lt;code&gt;go mod tidy&lt;/code&gt; which pulls in all the dependencies and recreates &lt;code&gt;go.sum&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;After all that, I did a bit of testing to make sure it all worked. OK, to be honest, there are still a bunch of older projects I haven&amp;rsquo;t cleaned up yet, and some little used libraries that will need some updating. But I can create a new project and it works well from scratch, and it just takes a few minutes (usually) to update an old project.&lt;/p&gt;&#xA;&lt;p&gt;Actually, in the middle of doing all of this migration I learned that it might be possible to set up a sort of &amp;ldquo;vanity url&amp;rdquo; which points at the actual git repo where your code is. So if you change repos, you just need to change where that vanity url points. I assume that would live on a domain I own, so I could do something like &lt;code&gt;go get bit-101.com/bitlib&lt;/code&gt; and that would pull the code from wherever it actually lived. I still need to look into this, but no rush at this point. As long as Codeberg doesn&amp;rsquo;t abandon its mission any time soon.&lt;/p&gt;&#xA;&lt;h2 id=&#34;vim-plugins&#34;&gt;Vim plugins&lt;/h2&gt;&#xA;&lt;p&gt;In recent years, I made a few vim/neovim plugins. I&amp;rsquo;m just going to call it &amp;ldquo;vim&amp;rdquo; though I&amp;rsquo;m referring to both. There are several plugin managers for vim. Generally these also operate on a github repo. I happen to use &lt;code&gt;vim-plug&lt;/code&gt; where you specify the plugin with your github username and repo name like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Plug &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;bit101/bufkill&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most of the other plugin managers do something similar. The manager assumes github and looks for &lt;code&gt;github.com/bit101/bufkill&lt;/code&gt; - which works fine if your code is in github. I don&amp;rsquo;t think it recognizes any other git sites by default. Luckily, you can just put a full url there as well, like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Plug &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;https://codeberg.org/bit101/bufkill&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You might run into other situations with your languages or platforms that depend on github-specific paths. They&amp;rsquo;ll certainly be fixable.&lt;/p&gt;&#xA;&lt;h2 id=&#34;open-source&#34;&gt;Open Source&lt;/h2&gt;&#xA;&lt;p&gt;One thing I wasn&amp;rsquo;t fully aware of when I started moving things over was that Codeberg fully embraces free/libre/open source software, whatever you want to name it. If you&amp;rsquo;re creating a startup and have a bunch of private, proprietary code that&amp;rsquo;s copyrighted and trademarked, move on.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Codeberg is run by a non-profit association with an explicit mission of advancing the creation and development of free content and free and open-source software.&lt;/p&gt;&#xA;&lt;p&gt;This mission is shared by Codeberg&amp;rsquo;s contributors, donors and association members. We, and our servers, work tirelessly to provide you with our free service. However, there is one thing that we expect from you in return: To contribute back to the ecosystem by attaching a suitable license to the works that you put out in public, so as to let others reuse and adapt your works.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This was, in general, not a big problem for me. In fact, I&amp;rsquo;m wholeheartedly on board with that! Most of my repos were already open. But, I wound up with about eight private repos that I didn&amp;rsquo;t want to open up. Things like my dot files, a few client projects, some game projects that I might want to do something with, the source for a few of my sites and my personal wiki. Codeberg does say that if you are contributing to free software, they&amp;rsquo;ll allow you 100MB space for private repos. All told my private repos added up to about 65MB so I was OK, but didn&amp;rsquo;t want to worry about it and constantly have to check file sizes.&lt;/p&gt;&#xA;&lt;p&gt;By the way, Codeberg doesn&amp;rsquo;t get super heavy about this. It&amp;rsquo;s more like, &amp;ldquo;here&amp;rsquo;s how we look at things, please abide.&amp;rdquo; I didn&amp;rsquo;t see any threats about taking stuff down. In fact, they say if you really need a bit more space, get in touch and they&amp;rsquo;ll hear you out. This is about as heavy as I see them get:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;If you do not contribute to free/libre software (or if it is limited to your personal homepage), and we feel like you only abuse Codeberg for storing your commercial projects or media backups, we might get unhappy about that.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h3 id=&#34;forgejo&#34;&gt;Forgejo&lt;/h3&gt;&#xA;&lt;p&gt;Anyway, what I decided to do with my private repos is to create a private Forgejo install on my NAS. Codeberg itself is a fork of Forgejo, which is a fork of Gitea. I got this installed and moved my eight private repos over there. This is pretty neat to be honest. In fact, this very site is a Hugo static site, that is being stored in that very Forgejo install.&lt;/p&gt;&#xA;&lt;p&gt;I will admit that I&amp;rsquo;m a bit worried about self-hosting important files like this. But my NAS has:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A ZRAID1 configuration with one drive of redundancy&lt;/li&gt;&#xA;&lt;li&gt;Regular snapshots that are saved for two weeks&lt;/li&gt;&#xA;&lt;li&gt;A daily incremental backup to another device using Restic, with weekly pruning, keeping 7 daily, 5 weekly, 12 monthly and several yearly snapshots&lt;/li&gt;&#xA;&lt;li&gt;A daily sync to a cloud provider&lt;/li&gt;&#xA;&lt;li&gt;Regular SMART tests on all disks&lt;/li&gt;&#xA;&lt;li&gt;Regular scrubs of the datasets&lt;/li&gt;&#xA;&lt;li&gt;All of the above fully automated with monitoring and alerting if anything goes wrong&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve never had such serious data protection in my life. And I have some old Flash source files that are 27 years old and have bounced around a dozen or more computers and a Dropbox account. I think I should be OK. But yeah, I still worry.&lt;/p&gt;&#xA;&lt;h3 id=&#34;licenses&#34;&gt;Licenses&lt;/h3&gt;&#xA;&lt;p&gt;I took heed of Codeberg&amp;rsquo;s statement mentioned above:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&amp;hellip;there is one thing that we expect from you in return: To contribute back to the ecosystem by attaching a suitable license to the works that you put out in public, so as to let others reuse and adapt your works.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;A lot of my repos had licenses on them, but&amp;hellip; it was probably less than half. With no license, they&amp;rsquo;re not really open source or free software. So they all have licenses now. I went with AGPL 3.0 for all the new ones. It&amp;rsquo;s a bit on the strict side. I used to use MIT most often, but never felt great about it. MIT&amp;rsquo;s license is basically, do whatever you want, just don&amp;rsquo;t come crying to me. After seeing so much abuse in recent years where billion dollar companies are using all this free and open source software and not supporting the authors at all, it seemed right to be a more strict. I don&amp;rsquo;t even think much of my stuff is in actual use anyway. I imagine people peek in to see how some particular effect was done, learn and adapt their own code to that. Which is fine with me in general.&lt;/p&gt;&#xA;&lt;p&gt;Oh, and for the record, all the Codeberg quotes above are from their &lt;a href=&#34;https://docs.codeberg.org/getting-started/faq/&#34;&gt;FAQ page&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;sizes&#34;&gt;Sizes&lt;/h3&gt;&#xA;&lt;p&gt;One final thing I wanted to check on was the sizes of my repos. Since I&amp;rsquo;m not dealing with a huge billion-dollar corporation, I didn&amp;rsquo;t want to be needlessly wasting space on their servers. The vast majority of my repos were under a megabyte. Most just a few kilobyes. But there were a few that were&amp;hellip; massive. Mostly where I had inadvertently checked in a bunch of images or even videos, or other large files. I&amp;rsquo;ve cleaned up the worst offenders. There are still a half dozen or so that are a few (less than five) megabytes. I&amp;rsquo;ll get to those next.&lt;/p&gt;&#xA;&lt;h2 id=&#34;gitlab&#34;&gt;Gitlab&lt;/h2&gt;&#xA;&lt;p&gt;OMG, after all that, I remembered I had a gitlab account. I checked and there are 35 more repos in there!&lt;/p&gt;&#xA;&lt;p&gt;But most of them are just junk. Ideas I started and for some reason threw them up there rather than github, but never did much with. I think I can probably trash most of them. I&amp;rsquo;m not as worried about gitlab as I was with github, but they are also very much aboard the AI train. It&amp;rsquo;s billing itself as an &amp;ldquo;AI powered devsecops platform&amp;rdquo;. There&amp;rsquo;s just nothing in that description that I care about. I just want somewhere to put my code, keep track of changes and make it available to anyone else who might benefit from it. So I might as well clean up my tracks there too.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Remarkable 2</title>
      <link>https://bit-101.com/blog/posts/2025-08-28/remarkable-2/</link>
      <pubDate>Thu, 28 Aug 2025 18:20:48 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-08-28/remarkable-2/</guid>
      <description>&lt;p&gt;I think my last &amp;ldquo;review&amp;rdquo; of the Remarkable was a bit too high level and focused more on strategy decisions the company made that I didn&amp;rsquo;t agree with. This one will focus more on features.&lt;/p&gt;&#xA;&lt;p&gt;I made a bunch of notes (on the Remarkable) about all the different features I could think of, and whether I thought they were good (and why), OK, or needed some improvement. Here we go!&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-pen-tools&#34;&gt;The Pen Tools&lt;/h3&gt;&#xA;&lt;p&gt;Overall the drawing / writing tools are very nice. The calligraphy / fountain pen one is pretty amazing, though I don&amp;rsquo;t really use it all that much. I mostly use the fineliner pen on the thinnest width in black for writing. It&amp;rsquo;s not pressure sensitive, so you get a consistent line. The ballpoint pen is very similar but can fade or get thicker depending on your pressure.&lt;/p&gt;&#xA;&lt;p&gt;I would like more variety in line widths. Most tools only give you three widths, thin, medium, thick. I&amp;rsquo;d like to get somewhere in between thin and medium on the fineliner.&lt;/p&gt;&#xA;&lt;img alt=&#34;different drawing styles on the remarkable&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-08-28/remarkable.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The highlighter is the other one I uses a lot. It only has a default width, and only one shade of gray. There are other colors but on a non-color screen, they all look pretty much the same for the most part.&lt;/p&gt;&#xA;&lt;p&gt;If you draw a line, rectangle or other quadrilateral, triangle, circle or ellipse, and then hold at the end, it will create a perfect representation of that shape, replacing the sketchy version you made. If you continue holding, you can scale and rotate it. I use that all the time for drawing borders around text or even making &amp;ldquo;check boxes&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;The eraser is good. You can tap on the eraser tool and erase exactly what the eraser touches, or you can switch to a selection eraser which erases what you select. Or, if you have the Marker Pro, you can flip the pen and erase with the opposite end. That&amp;rsquo;s good for large, quick erasing, but not as accurate. One thing I miss from the Onyx Boox drawing tools is a vector eraser - where any line you touch at all with the eraser will be removed.&lt;/p&gt;&#xA;&lt;p&gt;The select tool is very nice, though it did take a while to realize you have to fully select around whatever you want to select, not just intersect with it. This can get tricky if you want to select a certain amount of text and move it. I&amp;rsquo;ll often miss a few random strokes.&lt;/p&gt;&#xA;&lt;p&gt;When you have a selection, you can scale and rotate, copy, cut and paste, even across documents. Scaling always keeps the original aspect ratio. It would be nice to stretch in one axis or the other, but no big deal.&lt;/p&gt;&#xA;&lt;h3 id=&#34;typed-text&#34;&gt;Typed text&lt;/h3&gt;&#xA;&lt;p&gt;You can enter typed text via the on-screen keyboard or the official keyboard cover or if you want to hack a bit, with an external usb keyboard.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not really interested in this, but tried it out, and I&amp;rsquo;m glad I&amp;rsquo;m not interested in it because it&amp;rsquo;s kind of bad.&lt;/p&gt;&#xA;&lt;p&gt;I expected to be able to place a text box somewhere in the page and type into it. And then select and move that text around or rotate and scale it, like in every other drawing program you can add text to.&lt;/p&gt;&#xA;&lt;p&gt;But it&amp;rsquo;s not like that. It&amp;rsquo;s really more like a wordprocesser where you just type on the whole page. You can then add drawings or handwritten text, but the two don&amp;rsquo;t really mix well. If you draw and then type text, or vice versa, and then type or delete lines, it moves the handwritten material around.&lt;/p&gt;&#xA;&lt;p&gt;If you delete a bunch of text, the handwritten material may scroll up and right off the top of the page. I lost a paragraph or so of handwritten notes that way.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;d suggest if you do one or the other on a given page. Don&amp;rsquo;t mix.&lt;/p&gt;&#xA;&lt;p&gt;Personally, I just avoid typing text. It wasn&amp;rsquo;t something I planned to use anyway.&lt;/p&gt;&#xA;&lt;p&gt;You can also select handwritten text and convert it to typed text. This seems to work really well, but again, not a big use case for me, at least right now. Maybe I&amp;rsquo;ll find a reason to use this in the future.&lt;/p&gt;&#xA;&lt;h3 id=&#34;gestures&#34;&gt;Gestures&lt;/h3&gt;&#xA;&lt;p&gt;There are various gestures you can use with your fingers.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Two finger tap - undo&lt;/li&gt;&#xA;&lt;li&gt;Three finger tap - redo&lt;/li&gt;&#xA;&lt;li&gt;Left/right one finger swipe, change pages or create new page&lt;/li&gt;&#xA;&lt;li&gt;Single finger swipe down, extends the current page&lt;/li&gt;&#xA;&lt;li&gt;Two finger movement pans the document&lt;/li&gt;&#xA;&lt;li&gt;Two finger swipe left/right on pdf creates a margin area to take notes&lt;/li&gt;&#xA;&lt;li&gt;Swiping up or down from the top or bottom edge with one or two fingers navigates you to different parts of the app - file manager, favorites/recents, page overview&lt;/li&gt;&#xA;&lt;li&gt;Standard pinch to zoom in/out&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Most of these are very good. But get a little confusing, and sometimes don&amp;rsquo;t register 100%, but not too bad. I am not a fan of the continuous page thing and for a while I kept accidentally extending my page by swiping vertically when I wanted to change to the next page.&lt;/p&gt;&#xA;&lt;h3 id=&#34;layers&#34;&gt;Layers&lt;/h3&gt;&#xA;&lt;p&gt;Each page can have multiple layers, like many graphics tools. The bottom layer is hard coded to be the background, which can be a template for notes, or the pdf itself if you&amp;rsquo;re viewing a pdf.&lt;/p&gt;&#xA;&lt;p&gt;As mentioned, I wish custom templates were supported officially, though working around it isn&amp;rsquo;t hard if you are technically adept.&lt;/p&gt;&#xA;&lt;p&gt;Beyond templates, I haven&amp;rsquo;t used layers at all, but they seem fine.&lt;/p&gt;&#xA;&lt;h3 id=&#34;page-management&#34;&gt;Page Management&lt;/h3&gt;&#xA;&lt;p&gt;In a single doc you can see an overview of pages, add pages, rearrange them, delete them, etc. All works fine and fairly intuitive.&lt;/p&gt;&#xA;&lt;p&gt;You can also choose what to show as a thumbnail for the doc - the last page you were looking at, or the first page of the doc. I&amp;rsquo;ve made custom cover pages for most of my docs that clearly show what they are in the doc overview screens.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-tool-bar&#34;&gt;The Tool Bar&lt;/h3&gt;&#xA;&lt;p&gt;Pretty straightforward with the tools you mostly use up front. A few more features hidden with a three-dot menu. The whole thing is easily hideable.&lt;/p&gt;&#xA;&lt;p&gt;It would be nice if there were some customization possible, but what you get is fine.&lt;/p&gt;&#xA;&lt;h3 id=&#34;search-tags-file-management-etc&#34;&gt;Search, Tags, File Management, etc.&lt;/h3&gt;&#xA;&lt;p&gt;You can search typed text, tags, document names and that all works fine.&lt;/p&gt;&#xA;&lt;p&gt;In the latest beta, you can even search handwritten text.&lt;/p&gt;&#xA;&lt;p&gt;THIS IS MAGICAL!&lt;/p&gt;&#xA;&lt;p&gt;I can search all my documents for a single word and it can figure out my crappy handwriting and display all the results with amazing precision.&lt;/p&gt;&#xA;&lt;p&gt;I haven&amp;rsquo;t used tags at all yet. And haven&amp;rsquo;t really needed to search much yet, but I imagine after I have a lot more content, this will all be super useful.&lt;/p&gt;&#xA;&lt;p&gt;The whole file management system is very straightforward and fine. You have nested folders with documents in them. What more do you need?&lt;/p&gt;&#xA;&lt;h3 id=&#34;linux-stuff&#34;&gt;Linux Stuff&lt;/h3&gt;&#xA;&lt;p&gt;You can SSH into the device - via USB at first, but once you&amp;rsquo;re in you can activate WIFI-based SSH. You can use passwords or ssh keys. Then you have a Linux file system and tools, like vim, nano, all the usual utils. Wonderful. You can copy files to or from it with sftp, scp, rsync. Lovely.&lt;/p&gt;&#xA;&lt;p&gt;Sadly, though there are mobile apps for Android and iOS and desktop apps for Win and Mac, there&amp;rsquo;s no Linux desktop app. And there&amp;rsquo;s an official Chrome extension for sending web pages to the device, but nothing for Firefox. There are unofficial ones that work fine though.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s also a MyRemarkable web site that lets you send documents to the device. I haven&amp;rsquo;t really used it, but it&amp;rsquo;s nice to have there.&lt;/p&gt;&#xA;&lt;h3 id=&#34;physical&#34;&gt;Physical&lt;/h3&gt;&#xA;&lt;p&gt;The Marker Pro is good. The magnet where it sticks to the device is solid. The leather case I overpaid for is nice. Wish it did the auto-sleep/wake thing though. The screen size is the same as my Onyx Boox Note Air, but the Remarkable has much bigger bezels, so the device is a bit larger (though thinner).&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;Almost all of these features work great and are well thought out and have decent defaults. My biggest complaint would be that I&amp;rsquo;d like a bit more choice or customization of things, like line widths and more shades of gray. The typed text set up is awful in my opinion, but I never planned to use it anyway.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Remarkable</title>
      <link>https://bit-101.com/blog/posts/2025-08-24/remarkable/</link>
      <pubDate>Sun, 24 Aug 2025 12:41:03 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-08-24/remarkable/</guid>
      <description>&lt;p&gt;Well, I finally did it. I went and got myself a Remarkable 2 eInk tablet.&lt;/p&gt;&#xA;&lt;h2 id=&#34;history&#34;&gt;History&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m a big fan of eInk. I got my first Kindle in 2009 and have been doing most of my reading on eInk tablets ever since. After a few Kindles I wanted something a bit larger and was really interested in writing, drawing, sketching on eInk. So I got a Onyx Boox Nova tablet. That device was around 8&amp;quot; and included a stylus for drawing. A bit later I wanted something larger and got on Onyx Boox Note Air - a 10.3&amp;quot; tablet with drawing capabilities. I loved that but silly me, I wanted something a bit more portable - pocket sized, so got an Onyx Boox Poke 3. This had the same screen size as the Kindle Paperwhite I had just before that, but much smaller bezels, so utterly pocketable. Sadly, it was so pocketable, that I sat on it and cracked the screen. I replaced it with an Onyx Boox Go 6, which is essentially the same device with a new name.&lt;/p&gt;&#xA;&lt;p&gt;The Note Air is great for reading PDFs or books with code or lots of diagrams. I&amp;rsquo;ll still use it often for that, but most of my day to day reading is done on the smaller Go 6. The writing/drawing capabilities of the Note Air are pretty good. I&amp;rsquo;ve sketched out a ton of ideas, often when I&amp;rsquo;m doing some creative coding or trying to work out some trig problem. I did try and use it a few times for organization, general note taking etc. But it started to show its weaknesses there. I&amp;rsquo;d say it&amp;rsquo;s a great ebook reader with an OK drawing/sketching ui.&lt;/p&gt;&#xA;&lt;h2 id=&#34;enter-the-remarkable&#34;&gt;Enter the Remarkable&lt;/h2&gt;&#xA;&lt;p&gt;I watched the coming of the Remarkable with some interest but never saw it as compelling enough to take the plunge. Although it can display PDFs and epub books, it doesn&amp;rsquo;t have many advanced ebook reading functionality. It&amp;rsquo;s bare bones in that area and doesn&amp;rsquo;t have a ton of storage, so I was happy with my Onyx devices. It seems like the drawing/writing aspect was really good though. But would the upgrade be worth the price? Did I want an eInk device as my note taking and organization device? It wasn&amp;rsquo;t a stunning success on the Note Air, so I wasn&amp;rsquo;t sure.&lt;/p&gt;&#xA;&lt;p&gt;But then more and more people I knew got Remarkables and raved about them. Finally a good friend / coworker got one a few weeks ago and it really got me looking deeper. I read a lot and watched a lot of videos and decided it wasn&amp;rsquo;t for me.&lt;/p&gt;&#xA;&lt;p&gt;But I kept thinking about it.&lt;/p&gt;&#xA;&lt;p&gt;Then read some more and watched some more and bought one. It arrived earlier this week.&lt;/p&gt;&#xA;&lt;h2 id=&#34;warming-up&#34;&gt;Warming Up&lt;/h2&gt;&#xA;&lt;p&gt;I knew I wanted to go all in on the organization aspect of the Remarkable and after digging around, I purchased a customizable PDF workbook from On:Planners. I got the &lt;a href=&#34;https://onplanners.com/templates/remarkable-ultimate-planner&#34;&gt;&amp;ldquo;Ultimate Organizer for Remarkable&amp;rdquo;&lt;/a&gt;. It wasn&amp;rsquo;t all that expensive, and the cool thing is that you can customize the hell out of it. Which pages to you want to show for each year, quarter, month, week and day? A whole bunch of other templates and layouts you can add on to the end of it. When you&amp;rsquo;re done planning your planner, you export the PDF. Don&amp;rsquo;t like it? Go back to the config page and change it and export again, unlimited times.&lt;/p&gt;&#xA;&lt;p&gt;I created the planner I want and although it is designed for the Remarkable, it fit well on my Note Air. So I made two copies, one for work and one for personal and started using them there for a few days while waiting for my Remarkable 2 to ship.&lt;/p&gt;&#xA;&lt;h2 id=&#34;organizing&#34;&gt;Organizing&lt;/h2&gt;&#xA;&lt;p&gt;I hate to sound dramatic, but it&amp;rsquo;s been kind of life changing. I&amp;rsquo;ve gotten in the habit of writing everything down and have realized how often I have a thought and don&amp;rsquo;t write it down and five minutes (OK, one minute) later I&amp;rsquo;m like, &amp;ldquo;what was that thought?&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;Now I&amp;rsquo;m trying to catch those as they come up. If my Remarkable is not at hand, I try to capture it some other way. I was using Google assistant voice recognition to make notes, but I told it I wanted to &amp;ldquo;buy dental floss&amp;rdquo; and &amp;ldquo;charge my Garmin (running watch)&amp;rdquo; and it made notes to &amp;ldquo;eye dental plus&amp;rdquo; and &amp;ldquo;George Gorman&amp;rdquo;. No lie. I wonder if they&amp;rsquo;ve dumbed down the original assistant so they could push people to Gemini. I don&amp;rsquo;t remember it being that bad. Anyway, I started carrying a small paper notebook and Space Pen in my pocket if I&amp;rsquo;m out and about, just for capturing stuff.&lt;/p&gt;&#xA;&lt;p&gt;Capturing all of those things I want to do and having them in a good, easily navigable system has been huge in improving my focus and productivity - both at work and personally.&lt;/p&gt;&#xA;&lt;h2 id=&#34;notes&#34;&gt;Notes&lt;/h2&gt;&#xA;&lt;p&gt;I started taking a lot of notes. Like in meetings. After doing this all week I realized something - notes are not super useful for the future. How often does anyone really go back and read old meeting notes? But they are super useful in the present, helping me to stay focused and more fully understand what&amp;rsquo;s going on. If I don&amp;rsquo;t know what to write about something that was said, I find I usually don&amp;rsquo;t understand it and I can clarify it then and there. And the act of writing it helps me remember it better even if I never look at the notes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;but-the-remarkable&#34;&gt;But&amp;hellip; the Remarkable?&lt;/h2&gt;&#xA;&lt;p&gt;Oh yeah, that&amp;rsquo;s what this is about. So I was doing all this organization and note taking all week - first on the Note Air, and then on the Remarkable.&lt;/p&gt;&#xA;&lt;p&gt;I have to say&amp;hellip; it was night and day. The Note Air is fine, but very klunky in terms of navigation. PDF workbooks (that you write on) are in one part of the device, but notes, which can have layers and a template, live in another part of the device. And the UI for drawing/writing is a quite a bit different in the two apps, so it gets confusing. Palm rejection is not great, so I was always accidentally navigating away from where I was on the Note Air - super annoying.&lt;/p&gt;&#xA;&lt;p&gt;Pretty much all of those complaints went away on the Remarkable. All docs, whether notes or PDFs just live in the pseudo-file-system. And you get access to recents and favorites as well, so swapping to some other document you want is generally pretty easy.Writing and palm rejection is fantastic.&lt;/p&gt;&#xA;&lt;p&gt;Now every review you will see on the Remarkable gushes about how it &amp;ldquo;feels just like paper!&amp;rdquo; and &amp;ldquo;it even sounds like paper (holds microphone up to the tablet and writes, so you can hear the scratchy sounds)&amp;rdquo;. I&amp;rsquo;m surprised nobody is reporting it smells like paper. Whatever. The pen and screen are good and it&amp;rsquo;s not like you&amp;rsquo;re writing on glass, so that&amp;rsquo;s nice. I could care less what it sounds like.&lt;/p&gt;&#xA;&lt;h2 id=&#34;complaints&#34;&gt;Complaints&lt;/h2&gt;&#xA;&lt;p&gt;Some of these are minor, some more serious.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;This is a very premium device.&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I mean that from a cost perspective mostly. Yes, it has a nice build and good components, but it&amp;rsquo;s freaking expensive. Especially considering how much of a single use device it is. I&amp;rsquo;m not complaining about the single use aspect. I like that, but it costs a LOT for a single use device.&lt;/p&gt;&#xA;&lt;p&gt;Not just the device is expensive. The cases, OMG. The case with a keyboard in it is like $200. The leather one is like $170. The cloth case (CLOTH!!!) is like $130. WTH? They are nice cases. The device snaps into them solidly with a magnet. But $130 for a piece of cloth wrapped around a piece of cardboard with a magnet in it?&lt;/p&gt;&#xA;&lt;p&gt;As it is, I bit the bullet and got the leather case and upgraded pen. Spent a lot more than I should have, but YOLO.&lt;/p&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lack of expected basic features.&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not talking about fancy bells and whistles, but things that just surprised me.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;None of the cases support wake/sleep on open/close&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;There&amp;rsquo;s no light on the device (Remarkable 2, that is. The newer color Paper Pro version does have one).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Pretty small storage (8 GB) and no way to upgrade.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;None of these are deal breakers, but it really surprised me that a device in such a premium price range would skip all these things.&lt;/p&gt;&#xA;&lt;ol start=&#34;3&#34;&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lock in.&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I have no interest in the keyboard case, but it&amp;rsquo;s the only supported way to use a keyboard with the device. There&amp;rsquo;s no Bluetooth and USB keyboards are not recognized.&lt;/p&gt;&#xA;&lt;p&gt;Since the Remarkable runs on Linux, smart people have written programs to make using USB keyboards possible, but you gotta be a bit tech savvy. Supporting external keyboards is something they easily could have made possible, but they chose not to in order to sell their own add on.&lt;/p&gt;&#xA;&lt;ol start=&#34;4&#34;&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lock in.&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I know I already said that, but there&amp;rsquo;s more. Templates. The Remarkable comes with a good handful of templates for various grids, lines, planners, etc. If you sign up for the paid Connect service $29 per year, you get access to a bunch more.&lt;/p&gt;&#xA;&lt;p&gt;But there is no supported way to add your own templates. No &lt;em&gt;supported&lt;/em&gt; way. Again, if you have the know how to SSH into the device, navigate a Linux file system on the command line and edit a JSON file, adding your own templates is a piece of cake. And if you do all that, your custom templates will get wiped out when you update the device, so be prepared to set up a way to copy them over again.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve already made three custom templates that I use every day.&lt;/p&gt;&#xA;&lt;ol start=&#34;5&#34;&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lock in.&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Yup, I said it again. This time for the syncing system. If you register your device, it will store all your documents in the cloud. With a caveat. Any backed up document that hasn&amp;rsquo;t been edited in a certain amount of time (50 days I think) will be removed. It won&amp;rsquo;t be removed from your device, but it will no longer get backed up. Of course, you can overcome this limitation by signing up for the Connect service ($29 per year).&lt;/p&gt;&#xA;&lt;p&gt;You can also back up docs to Google Drive, One Note, Dropbox. But I &lt;em&gt;think&lt;/em&gt; this just backs them up as PDFs, not the editable native format. I don&amp;rsquo;t plan to go that route, so I don&amp;rsquo;t know the details for sure.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;d kind of rather they just made the backup service paid. You pay, we back up. You don&amp;rsquo;t, we don&amp;rsquo;t. But to do that, they&amp;rsquo;d have to offer some kind of local backup functionality. That would be nice, honestly.&lt;/p&gt;&#xA;&lt;p&gt;Instead, if you don&amp;rsquo;t pay for Connect you get a confusing half-assed free backup service where you never know what&amp;rsquo;s backed up and what isn&amp;rsquo;t. That is horrible.&lt;/p&gt;&#xA;&lt;p&gt;Once again, if you have some Linux experience, it&amp;rsquo;s super easy to SSH in to the device and find out where the docs are. I set up an rsync script that copies them to my laptop. Piece of cake.&lt;/p&gt;&#xA;&lt;h2 id=&#34;verdict&#34;&gt;Verdict&lt;/h2&gt;&#xA;&lt;p&gt;Again, the complaints about missing basic features (auto sleep/wake, light, storage) are really more surprises. I can live without them.&lt;/p&gt;&#xA;&lt;p&gt;The lock in stuff is more concerning. The fact that they explicitly made simple things &amp;ldquo;impossible&amp;rdquo; (local backup, custom templates, external keyboard) in order to sell exclusive services and addons is very disappointing. For the premium price they are charging for the device, they should make these things possible to someone who is not a complete Linux nerd. It makes me question the company&amp;rsquo;s attitude towards its customers and gives me concern over what they will lock down next.&lt;/p&gt;&#xA;&lt;p&gt;Luckily, I am a Linux nerd! So I can overcome all of these, I have my custom templates and local backup. I don&amp;rsquo;t need a keyboard but if I change my mind, I can hack one in. Honestly, once you dive into the Linux aspect, you realize it&amp;rsquo;s a great hacker device!&lt;/p&gt;&#xA;&lt;p&gt;So, in spite of sounding super negative in all of the above, I&amp;rsquo;m happy I got the the Remarkable. No buyer regret. It&amp;rsquo;s a keeper.&lt;/p&gt;&#xA;&lt;p&gt;In short, I love the device itself but I question some of the decisions of the company that made it.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll probably post occasional tips or tricks I discover. Anything that&amp;rsquo;s not already been mentioned by a hundred other Remarkable users.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>NAS Part II</title>
      <link>https://bit-101.com/blog/posts/2025-08-23/nas-part-ii/</link>
      <pubDate>Sat, 23 Aug 2025 16:50:00 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-08-23/nas-part-ii/</guid>
      <description>&lt;p&gt;In my last post (&lt;a href=&#34;https://bit-101.com/blog/posts/2025-07-20/i-have-a-nas/&#34;&gt;I have a NAS!&lt;/a&gt;) I talked about the NAS I created with TrueNAS. This has been fantastic. I wanted to write more because:&lt;/p&gt;&#xA;&lt;p&gt;A. I wrote too much the first time but still didn&amp;rsquo;t cover everything.&lt;/p&gt;&#xA;&lt;p&gt;B. I&amp;rsquo;ve done more stuff with it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;catching-up&#34;&gt;Catching up&lt;/h2&gt;&#xA;&lt;p&gt;In the first post I talked about the initial setup, replacing one of the drives, the apps I installed, monitoring, and syncing to devices.&lt;/p&gt;&#xA;&lt;p&gt;A couple more things about all that, and things I&amp;rsquo;ve done since then.&lt;/p&gt;&#xA;&lt;h2 id=&#34;syncthing-errors-be-gone&#34;&gt;SyncThing errors be gone&lt;/h2&gt;&#xA;&lt;p&gt;I was still having occasional glitches with SyncThing. Not so much conflicts, but not connecting right, not updating, getting into weird states, etc. The thing that finally fixed it all for me was realizing that Android&amp;rsquo;s file system is not the same as what&amp;rsquo;s on the NAS or my other computers. While I kept ignoring dynamic manifest files like &lt;code&gt;.sdr&lt;/code&gt; for KOReader, what really heped was disabling syncing of file permissions, and making sure that it was also ignoring extended attributes. I went through days of things not syncing right. But once I got rid of permission syncing, it started working perfectly and I don&amp;rsquo;t think I&amp;rsquo;ve had a glitch or conflict in weeks.&lt;/p&gt;&#xA;&lt;h2 id=&#34;omg-lyrics&#34;&gt;OMG Lyrics!&lt;/h2&gt;&#xA;&lt;p&gt;One added, unexpected, and kind of magical thing that happened was when I added a music lyrics fetching plugin to Jellyfin. It took a bit, but it scanned my whole library and downloaded lyrics for the vast majority of files that actually have lyrics (non-instrumental songs). That was nice. But magically, those lyrics synced to my two portable digital audio players, putting those lyrics files on those devices too. And suddenly I have full lyrics support for most of my music library everywhere I use it. This is amazing. A while back I wanted to get some lyrics, but doing it by hand&amp;hellip; impossible. I paid for a MusixMatch subscription for a bit, but didn&amp;rsquo;t really like it all that much. Now it&amp;rsquo;s just there, and backed up forever.&lt;/p&gt;&#xA;&lt;img alt=&#34;the flat earth lyrics - thomas dolby&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-08-23/flat_earth.jpg&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;real-backup&#34;&gt;Real backup&lt;/h2&gt;&#xA;&lt;p&gt;There&amp;rsquo;s the &amp;ldquo;3-2-1&amp;rdquo; backup rule: 3 copies of your data, 2 different kinds of media, 1 off site. I have an offsite sync to pCloud. But what concerned me with that was that if I accidentally deleted or corrupted a bunch of files, they&amp;rsquo;s sync to pCloud and delete/corrupt there too. There&amp;rsquo;s some level of history there, 30 days or something, but it didn&amp;rsquo;t feel great. I wanted a true incremental backup.&lt;/p&gt;&#xA;&lt;p&gt;My old &amp;ldquo;home lab&amp;rdquo; HP slim device was sitting unused. It only has space for a 2.5 inch drive though. So I got another 3.5 inch 4 TB drive and put that into the NAS. Did the whole disconnect, replace, resilver thing I did earlier. Worked perfectly. Now that has three 3.5 inch 4 TB drives and I put the 2.5 inch I removed from that and put it in the other HP.&lt;/p&gt;&#xA;&lt;p&gt;I installed TrueNAS on that too! But very minimal. The only app I put on there was Tailscale, but I&amp;rsquo;ll probably never use that there anyway. Then I set up &lt;a href=&#34;https://github.com/restic/restic&#34;&gt;restic&lt;/a&gt; on my main NAS using the new backup NAS as a target. This is great. It does an incremental backup every day. Once a week it does a prune, keeping 7 daily backups, 5 weekly, 12 monthly and some number of yearly backups.&lt;/p&gt;&#xA;&lt;p&gt;A cool thing about restic is that you can just mount your backup on any computer that has restic installed. I can do that from the NAS itself or even my laptop. Then I can browse any saved snapshot as if it were a large filesystem. I can mount it from my laptop, go into a snapshot I did months ago and grab a file that has since been deleted. That&amp;rsquo;s super useful. Of course, there&amp;rsquo;s full restore capabilities as well.&lt;/p&gt;&#xA;&lt;h2 id=&#34;frankensteins-nas&#34;&gt;Frankenstein&amp;rsquo;s NAS&lt;/h2&gt;&#xA;&lt;p&gt;I mentioned that I now had three 3.5 inch drives in the NAS. But that machine only has space for two 3.5s and one 2.5. After much mental engineering, I got a hard drive bracket that I put on one of the internal drives that elevates outside of the case, giving me a place to install another 3.5 inch drive. Of course this means I can&amp;rsquo;t put the cover on the case. So be it.&lt;/p&gt;&#xA;&lt;p&gt;Similarly, I discovered when I put the 2.5 inch drive in the backup box, it&amp;rsquo;s a thick drive, and the 2.5 inch mount there doesn&amp;rsquo;t support that. I did a bit of surgery and got it in there, but now that cover doesn&amp;rsquo;t fit either!&lt;/p&gt;&#xA;&lt;p&gt;Even with the covers off, as I was monitoring the drives, they were getting fairly warm. They were getting into the high 40s C. Not sure if any made it to 50 C, but some hit 49 for sure. That&amp;rsquo;s kind of borderline from what I&amp;rsquo;ve read. 60 C is heading into the danger zone. 50 &amp;hellip; probably OK, but not great. This was on both machines. Doesn&amp;rsquo;t help that we&amp;rsquo;re in the middle of a two-month heat wave here in the Boston area.&lt;/p&gt;&#xA;&lt;p&gt;Since these are custom slim form factor cases, there&amp;rsquo;s no space to mount fans, and no extra headers to power them anyway. So I picked up a couple of USB powered fans. There&amp;rsquo;s plenty of USB ports! Jockeyed these into rough position so they are blowing on the drives and voila! They all stay in the mid 30s C mostly. Even lower on a cool night. That&amp;rsquo;s with the fans on low. An extended drive activity session might get them up to the low 40s. I&amp;rsquo;ll take it.&lt;/p&gt;&#xA;&lt;img alt=&#34;a set of usb powered fans&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-08-23/fans.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Am I proud of this mess of computer parts held together with duct tape and paperclips? Damn right I am! But more on that later.&lt;/p&gt;&#xA;&lt;h2 id=&#34;metube&#34;&gt;MeTube&lt;/h2&gt;&#xA;&lt;p&gt;One more thing I wanted to do was try to cut back my YouTube doom scrolling. There&amp;rsquo;s plenty of stuff I want to watch. Some channels I subscribe to, various things I want to learn about, etc. But all too often I&amp;rsquo;ll go there looking for one thing and an hour later I&amp;rsquo;ve watched twenty stupid videos about god-knows-what. I don&amp;rsquo;t even remember, but some of them had to do with cute animals.&lt;/p&gt;&#xA;&lt;p&gt;I set up another video library in Jellyfin. This is simply a folder on the NAS that it will scan for videos. Then I installed &lt;a href=&#34;https://github.com/alexta69/metube&#34;&gt;MeTube&lt;/a&gt; on the NAS. It&amp;rsquo;s one of the TrueNAS supported apps. Then I installed a MeTube extension in my browser. Now I can right click on a video thumbnail and there&amp;rsquo;s a context manu item, &amp;ldquo;Send to MeTube&amp;rdquo;. Or if I&amp;rsquo;m on a video page I can just click the plugin icon and it does the same thing. A few minutes later, the video appears in my Jellyfin library.&lt;/p&gt;&#xA;&lt;p&gt;This is my Jellyfin setup for media. I&amp;rsquo;m pretty happy with the styling I did here. :)&lt;/p&gt;&#xA;&lt;img alt=&#34;media tiles&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-08-23/media.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And this is what&amp;rsquo;s in there right now:&lt;/p&gt;&#xA;&lt;img alt=&#34;media tiles&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-08-23/library.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Generally I try not to watch anything ON YouTube now. I&amp;rsquo;ll scroll through and send what looks interesting to MeTube and then I have a nice playlist to go through later. For the most part I delete the videos after I watch them unless it&amp;rsquo;s something I&amp;rsquo;d really want to keep around.&lt;/p&gt;&#xA;&lt;h2 id=&#34;photos&#34;&gt;Photos&lt;/h2&gt;&#xA;&lt;p&gt;I also have a ton of photos going decades back on the NAS. I&amp;rsquo;ve previously used &lt;a href=&#34;https://immich.app/&#34;&gt;Immich&lt;/a&gt; which is a supported app on TrueNAS, but I could not get it to work, and to be honest, while it&amp;rsquo;s great, it&amp;rsquo;s pretty heavy, with AI image recognition and categorization, etc. I just wanted to have my pictures available. Jellyfin supports photos too, so there they are. I put a ton of work into cleaning up duplicates and stuff I didn&amp;rsquo;t need to save, and arranging them all by year and month. They&amp;rsquo;re in the best shape they&amp;rsquo;ve ever been in at this point, and all backed up in multiple spots. This is good.&lt;/p&gt;&#xA;&lt;p&gt;Maybe someday I&amp;rsquo;ll give Immich another shot. Would be nice to have some search capabilities.&lt;/p&gt;&#xA;&lt;h2 id=&#34;future&#34;&gt;Future&lt;/h2&gt;&#xA;&lt;p&gt;As I said in the beginning, this whole project was a learning experience. I didn&amp;rsquo;t want to invest in an expensive prebuilt Synology or something, not knowing if that&amp;rsquo;s what I needed or wanted. Boy, have I learned a lot!&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve put enough time and energy (and not a tiny amount of money, though not all that bad) into this, so I&amp;rsquo;m going to run it as is for a while. But I think next year some time I&amp;rsquo;ll definitely be investing in a NAS case, maybe the &lt;a href=&#34;https://www.fractal-design.com/products/cases/node/node-804/black/&#34;&gt;Fractal Design Node 804&lt;/a&gt;&lt;/p&gt;&#xA;&lt;img alt=&#34;NAS case tiles&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-08-23/node.webp&#34; /&gt;&#xA;&#xA;&lt;p&gt;And a low-power/efficient ITX motherboard/CPU.&lt;/p&gt;&#xA;&lt;p&gt;Happy to hear any suggestions.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Command Line Tricks</title>
      <link>https://bit-101.com/blog/pages/command-line-tricks/</link>
      <pubDate>Mon, 28 Jul 2025 18:57:21 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/pages/command-line-tricks/</guid>
      <description>&lt;p&gt;Just a bunch of useful command line scripts to do various things. These are all prime candidates for aliases.&lt;/p&gt;&#xA;&lt;p&gt;For example, I alias &lt;code&gt;du -d 1 | sort -hr | column -t -R 1 -N Size,Folder -l 2&lt;/code&gt; to &lt;code&gt;dui&lt;/code&gt;. The &lt;code&gt;i&lt;/code&gt; doesn&amp;rsquo;t stand for anything, but it&amp;rsquo;s easy to remember!&lt;/p&gt;&#xA;&lt;h2 id=&#34;find-examples&#34;&gt;Find examples&lt;/h2&gt;&#xA;&lt;h3 id=&#34;find-files-by-name&#34;&gt;Find files by name.&lt;/h3&gt;&#xA;&lt;p&gt;Can add &lt;code&gt;-type f&lt;/code&gt; or &lt;code&gt;-type d&lt;/code&gt; to only look for files or directories.&lt;/p&gt;&#xA;&lt;p&gt;Simple glob patterns, not regex.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Case insensitive&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -iname &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Regex&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -regex &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;.*foo.*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Matching multiple names with AND.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt; -and -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*bar*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Shorter&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt; -a -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*bar*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And is implied.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt; -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*bar*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Matching multiple names with OR.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt; -or -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*bar*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Shorter&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt; -o -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*bar*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Reject file name matches. Can use &lt;code&gt;-and&lt;/code&gt; or &lt;code&gt;-a&lt;/code&gt; here too.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt; -not -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo-bar*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;find-files-by-extension&#34;&gt;Find files by extension.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*.ext&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;count-of-files-only&#34;&gt;Count of files only.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type f | wc -l&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;count-of-folders-only&#34;&gt;Count of folders only.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type d | wc -l&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;list-all-file-extensions-in-a-folder&#34;&gt;List all file extensions in a folder.&lt;/h3&gt;&#xA;&lt;p&gt;Simple&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type f | sed -e &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;s/.*\.//&amp;#39;&lt;/span&gt; | sed -e &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;s/.*\///&amp;#39;&lt;/span&gt; | sort -u&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With counts, and sorted&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type f | awk -F&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;{print $NF}&amp;#39;&lt;/span&gt; | sort| uniq -c | sort -gr&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;find-all-empty-directories&#34;&gt;Find all empty directories.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . &lt;span style=&#34;color:#366&#34;&gt;type&lt;/span&gt; -d -empty&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;find-all-empty-files&#34;&gt;Find all empty files.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . &lt;span style=&#34;color:#366&#34;&gt;type&lt;/span&gt; -f -empty&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;find-all-directories-that-dont-have-nested-directories-leaf-directories&#34;&gt;Find all directories that don&amp;rsquo;t have nested directories (leaf directories).&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;-links&lt;/code&gt; = 1 for current dir, 1 for parent dir, 1 for each nested dir.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type d -links &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;find-all-hidden-files&#34;&gt;Find all hidden files.&lt;/h3&gt;&#xA;&lt;p&gt;Hidden&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type f -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;.*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;NOT hidden&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type f -not -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;.*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;find-all-hidden-directories&#34;&gt;Find all hidden directories.&lt;/h3&gt;&#xA;&lt;p&gt;Hidden, ignoring current dir &amp;ldquo;.&amp;rdquo;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type d -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;.*&amp;#34;&lt;/span&gt; -not -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;NOT hidden&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type d -not -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;.*&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;delete-matches&#34;&gt;Delete matches.&lt;/h3&gt;&#xA;&lt;p&gt;Be careful here!!!&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -name &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*foo*&amp;#34;&lt;/span&gt; -delete&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;list-10-largest-files&#34;&gt;List 10 largest files.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find . -type f -exec ls -lhS &lt;span style=&#34;color:#555&#34;&gt;{}&lt;/span&gt; + | head -n &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;other&#34;&gt;Other&lt;/h2&gt;&#xA;&lt;h3 id=&#34;simple-list-of-directory-sizes&#34;&gt;Simple list of directory sizes.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dui -d &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; -h&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;folder-sizes-sorted-and-tabulated-change--d-to-control-depth&#34;&gt;Folder sizes sorted and tabulated. Change &lt;code&gt;-d&lt;/code&gt; to control depth.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;du -d &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; | sort -hr | column -t -R &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; -N Size,Folder -l &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;run-new-command-with-arguments-from-the-last-command&#34;&gt;Run new command with arguments from the last command.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;new_command &lt;span style=&#34;color:#033&#34;&gt;$_&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# or&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;new_command !$&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;recall-a-specific-argument-from-the-last-command&#34;&gt;Recall a specific argument from the last command.&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# single&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;new_command !:n&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# series&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;new_command !:n-m&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# arbitrary&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;new_command !:n !:p&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    <item>
      <title>I Have a NAS!</title>
      <link>https://bit-101.com/blog/posts/2025-07-20/i-have-a-nas/</link>
      <pubDate>Sun, 20 Jul 2025 11:06:27 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-07-20/i-have-a-nas/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve had some kind of home server / home lab for many years. Probably going back to 2008-ish. Generally this has been just a desktop computer running some Linux distro or another and connected to the internet. For many years, this was just an extra computer I could connect to, try things out, store files I didn&amp;rsquo;t need on my laptop (it usually had a decent amount of storage). I also often used it as a desktop computer for doing things if I happened to be sitting at the desk where it was.&lt;/p&gt;&#xA;&lt;p&gt;I used these desktops for somewhat of a &amp;ldquo;backup&amp;rdquo; but it was really just an extra copy of files in case my laptop fell off a cliff or something. A few years ago I started doing more structured, scheduled backups with restic. This worked out pretty well, but at some point it failed and I didn&amp;rsquo;t get around to fixing it for a while.&lt;/p&gt;&#xA;&lt;p&gt;As for external connectivity, at first I was just opening up a port on my router. Then one day I got a glimpse of the traffic that was trying to access that port and shut it down quick. I dodged a bullet there. Then I set up Wireguard. I had an vps in the cloud that was the main connection point. That could route traffic/services to the home server, or any other device on the Wireguard network.&lt;/p&gt;&#xA;&lt;p&gt;This all worked well for years and I wound up putting up various self-hosted services there. Music servers (Subsonic or compatible) and video servers (Plex, Jellyfin) mainly, but some things I created myself, and a lot of different self-hosted apps that I tested but didn&amp;rsquo;t end up keeping around very long.&lt;/p&gt;&#xA;&lt;p&gt;A few years ago I rebuilt my home desktop with a pretty powerful setup, somewhat of a gaming rig. I decided this was overkill to run as an always-on home server. So I got a thin client HP ProDesk device for cheap.&lt;/p&gt;&#xA;&lt;img alt=&#34;HP Prodesk mini computer&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/prodesk.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This is a tiny device. A bit bigger than an external DVD drive. It has room for an NVME SSD and a 2.5&amp;quot; drive. I plugged in a couple of external USB drives and I&amp;rsquo;ve been running that as my home server for over a year. It was running Ubuntu server, so my first headless server. But the USB connections were a bit funky.&lt;/p&gt;&#xA;&lt;p&gt;This is when I started eyeing NASes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;synology-qnap-diy-what-to-do&#34;&gt;Synology? QNAP? DIY? What to do?&lt;/h2&gt;&#xA;&lt;p&gt;I wasn&amp;rsquo;t sure what the NAS experience was. So many questions. Is this something I needed? Was a headless server with storage fine? Did I really need a RAID? What apps could I install on which types of NAS?&lt;/p&gt;&#xA;&lt;p&gt;Prebuilt NASes are expensive, even before adding drives. Even a 2-bay NAS is not cheap. And not expandable at all. Want to add a drive or a couple more? Nope. Get a new NAS with more bays. I didn&amp;rsquo;t want to sink many hundreds of dollars into this before I even knew whether it was something I needed.&lt;/p&gt;&#xA;&lt;p&gt;So I started looking at the DIY route. After reading many articles and watching many videos, I settled on a used HP EliteDesk 800 G3 SFF for under $90 US on Amazon.&lt;/p&gt;&#xA;&lt;img alt=&#34;HP EliteDesk computer&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/elitedesk.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;The SFF is for &amp;ldquo;small form factor&amp;rdquo;, but it&amp;rsquo;s a lot bigger than the ProDesk. Specs:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Processor: Intel Core 7th Gen i5 7500 3.4Ghz&lt;/li&gt;&#xA;&lt;li&gt;Memory: 16GB DDR4 RAM (2x8GB, leaving 2 slots open for more)&lt;/li&gt;&#xA;&lt;li&gt;Storage: 128GB NVME SSD&lt;/li&gt;&#xA;&lt;li&gt;Bays: 2x 3.5&amp;quot; drives, 1x 2.5&amp;quot; drive&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;It came with Windows 10 installed, but I never even booted into that. I was looking at Unraid, TrueNAS Core and TrueNAS Scale and went for the last one. Installed Scale and it booted up. I had two 4TB external HDDs that I broke open and extracted the 2.5&amp;quot; drives and put them in the HP. It recognized everything and I created a pool with the two drives in a mirrored VDEV, synced some files to it, messed around with the system and got a feel for it.&lt;/p&gt;&#xA;&lt;p&gt;But I wanted to eventually use all three storage slots and you can&amp;rsquo;t expand a mirrored VDEV, so I figured I&amp;rsquo;d do it right from the start and ordered another 3.5&amp;quot; 4TB drive. When that got in, I set up a RAIDZ1 VDEV. This gave me 8TB (7.14TiB) storage, with one drive used for parity.&lt;/p&gt;&#xA;&lt;img alt=&#34;raidz1 vdev with three drives&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/vdev.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Most of the files I wanted were on DropBox. I set up a cloud sync to pull all those files down. It was almost 1.4TiB of data, so it took a good 48 hours (DropBox does a lot of limiting). But with all that, I&amp;rsquo;m only using 18% of my storage.&lt;/p&gt;&#xA;&lt;img alt=&#34;chart showing 18.8% used of 7.14TiB&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/capacity.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;errors-oh-no&#34;&gt;Errors! Oh no!&lt;/h2&gt;&#xA;&lt;p&gt;As the data was syncing, I was running into occasional read/write errors on one of my drives. It was one of the 2.5&amp;quot; drives, which was a couple of years old, yanked out of that external HDD case. Not really shocking. This probably explained some of the errors I&amp;rsquo;d been seeing on my previous setup. I ran some S.M.A.R.T. tests on the drives and that same drive was consistently failing. But&amp;hellip; the errors were not happening very often. And hey&amp;hellip; parity disk. I&amp;rsquo;m already feeling the comfort of having a NAS with a RAID. So I let the download finish and planned on replacing the drive afterwards.&lt;/p&gt;&#xA;&lt;h2 id=&#34;apps&#34;&gt;Apps!&lt;/h2&gt;&#xA;&lt;p&gt;The main functionality I wanted was:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Music server&lt;/li&gt;&#xA;&lt;li&gt;Movie/TV server&lt;/li&gt;&#xA;&lt;li&gt;External connectivity (VPN)&lt;/li&gt;&#xA;&lt;li&gt;Syncing media to devices&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I know what you&amp;rsquo;re thinking&amp;hellip; &amp;ldquo;But that drive has errors! You need to fix it!&amp;rdquo; Yes, I ordered a drive, and started setting up my apps while I waited for that to arrive.&lt;/p&gt;&#xA;&lt;h3 id=&#34;jellyfin&#34;&gt;Jellyfin&lt;/h3&gt;&#xA;&lt;p&gt;For music and video I just went with Jellyfin. I switched over to Jellyfin from Plex earlier this year, when Plex more than doubled its prices while simultaneously reducing its features. I&amp;rsquo;ve been really impressed, especially with its music service. I&amp;rsquo;d been using PlexAmp for music, which is a great app on both desktop and mobile devices. Jellyfin is not quite as full featured, but does everything I needed.&lt;/p&gt;&#xA;&lt;img alt=&#34;Jellyfin&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/jellyfin.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;tailscale&#34;&gt;Tailscale&lt;/h3&gt;&#xA;&lt;p&gt;For external connectivity I switched over to Tailscale. I&amp;rsquo;d been wanting to do that for a while, but procrastinated for months just because Wireguard was working and I didn&amp;rsquo;t want to mess with something new. It turns out that Tailscale was super easy to set up - much easier than Wireguard. And is so much better. Wireguard sets up a server/client configuration, which was why I had the cloud vps as the server. But Tailscale is 100% peer to peer. Any device can access any other device. I think you might be able to do that with Wireguard, but I never figured out how. With Tailscale, that&amp;rsquo;s just the way it works out of the box. Once this was working I realized I had no need at all for the vps anymore. All my devices could talk to the NAS directly. Come to think of it, I probably could have just set up my home server as the Wireguard server and done the same thing. I think.&lt;/p&gt;&#xA;&lt;img alt=&#34;tailscale dashboard&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/tailscale.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;My Tailscale devices - Linux laptop, phone, tablet, NAS. Still need to set up my Macbook Air.&lt;/p&gt;&#xA;&lt;h3 id=&#34;syncthing&#34;&gt;SyncThing&lt;/h3&gt;&#xA;&lt;p&gt;There are a few devices I need to sync files to.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Two eInk e-readers (Onyx Boox Go 6 and Onyx Boox Note Air). All my books are de-DRMed and saved as epubs or pdfs on the NAS.&lt;/li&gt;&#xA;&lt;li&gt;A high res Digital Audio Player (DAP) (Hiby R4). Thousands of flacs and mp3s on the NAS.&lt;/li&gt;&#xA;&lt;li&gt;An Android tablet (Lenovo Tab). I also wany my books synced there, though I don&amp;rsquo;t use this much.&lt;/li&gt;&#xA;&lt;li&gt;My phone (Pixel 7 Pro). I like my books there too.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For this I used SyncThing. It syncs the books to the readers and tablets and the music files to the DAP. It was a bit fiddly to set this up, but I got there. I used to use an app called DropSync for this - an Android app that is kind of like rsync for DropBox. It works well, but I&amp;rsquo;m no longer relying on DropBox, so needed to change this.&lt;/p&gt;&#xA;&lt;p&gt;One consideration for this was that SyncThing wants to constantly monitor changes and sync constantly. Since all these devices are battery powered, I don&amp;rsquo;t want this happening 24-7. Also, it&amp;rsquo;s not like I&amp;rsquo;m adding books and music at all hours of every day. Maybe 1-2x a week, max. I set it up so that they&amp;rsquo;ll only connect if they are on AC power. This is great. If I add a book or album to the NAS, I just plug in the device and it will connect and sync.&lt;/p&gt;&#xA;&lt;img alt=&#34;Syncthing&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/syncthing.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here you can see that all my devices are disconnected. By design! I plug one in to AC power, and it will connect.&lt;/p&gt;&#xA;&lt;p&gt;And here are all my apps in TrueNAS as of this writing.&lt;/p&gt;&#xA;&lt;img alt=&#34;truenas apps dashboard&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/apps.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I&amp;rsquo;ll talk about Uptime Kuma a bit later.&lt;/p&gt;&#xA;&lt;h2 id=&#34;resilvering&#34;&gt;Resilvering&lt;/h2&gt;&#xA;&lt;p&gt;OK, OK, the new drive arrived. Nothing crashed in the meantime. Time to replace it. btw, I ordered some other hardware at the same time. HP does some funky mounting and cable routing in their SFF devices. It&amp;rsquo;s cramped and you need special screws for the drives to slot in correctly. Up to now, they&amp;rsquo;ve just been kind of balanced in there. I got the screws and some 90-degree connectors, which made everything a lot better.&lt;/p&gt;&#xA;&lt;p&gt;Following multiple forms of documentation, I first took the problem drive offline. You just do that in the TrueNAS UI. Then I went in and pulled the drive and plugged in the new one - while the system was running! WTF. I didn&amp;rsquo;t know this was possible. I know dedicated NASes do this, but figured it was some special magic hardware sauce. No, you can just hotswap a SATA drive - assuming it&amp;rsquo;s offline or unmounted I guess.&lt;/p&gt;&#xA;&lt;p&gt;As soon as the drive was offline, my pool warned me that it was in a degraded state. This means that everything will still function, but you should be careful about doing a lot of stuff with files. You now have no parity disk and if something goes wrong, that&amp;rsquo;s that.&lt;/p&gt;&#xA;&lt;p&gt;Next, I chose the offline drive and asked to replace it. I selected the new drive as the replacement and that took about a minute. But things were still degraded because this new drive was blank. It had to be &amp;ldquo;resilvered&amp;rdquo;. I didn&amp;rsquo;t really know what that meant, but I have somewhat of an idea now. With the RAIDZ1 array, two drive&amp;rsquo;s worth of data is spread across three drives. It keeps checksums so that if you lose one whole drive, it can use what it has, plus the checksums to reconstruct any missing data. It does that to reconstruct that new drive. It&amp;rsquo;s not just copying data, it&amp;rsquo;s calculating what needs to be there based on the other two drives and checksums. So this takes a LONG time. For my 1.34TiB of data, it took over 12 hours. But when it was done, it was back to full functionality.&lt;/p&gt;&#xA;&lt;p&gt;Although this was a bit of a pain in the neck to do right in the first week of setting up the NAS, I&amp;rsquo;m pretty happy it happened when it did. I learned a lot and when that other 2.5&amp;quot; drive goes (which I&amp;rsquo;m sure it will before long), I&amp;rsquo;ll know just what&amp;rsquo;s coming.&lt;/p&gt;&#xA;&lt;h2 id=&#34;backups&#34;&gt;Backups&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been kind of using DropBox as an offsite backup. But I&amp;rsquo;m kind of done with it. It&amp;rsquo;s a constant upsell and pushing all kinds of other new features on you all the time. After looking at a bunch of other cloud storage providers, I&amp;rsquo;m switching over to pCloud. It&amp;rsquo;s a bit cheaper than DropBox and it&amp;rsquo;s very focused on just data storage. Lots of good reviews.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve backed up my NAS there with just a straight sync. This is not really ideal because if I accidentally delete some stuff, it&amp;rsquo;s going to be deleted in pCloud too. I have 30 days to recover deleted files there, but it&amp;rsquo;s not the best in the long run. Better than DropBox though.&lt;/p&gt;&#xA;&lt;p&gt;What I&amp;rsquo;d like to get going is an actual versioned, progressive backup. I think I might be able to use restic to do that on pCloud. Or maybe there&amp;rsquo;s a way to do it with rclone, which is what TrueNAS uses by default there. That&amp;rsquo;s the next thing I want to look into. And probably doing a local restic backup on an external drive as well. Future stuff. At least it&amp;rsquo;s somewhere else for now.&lt;/p&gt;&#xA;&lt;p&gt;Also worth considering is that if my backup size goes over 2TB, I&amp;rsquo;ll have to figure something else out. The vast majority of that is music and movies. And I&amp;rsquo;m way more concerned about preserving my music. If I lost all my movies tomorrow, I&amp;rsquo;d actually be kind of ok with it. I might just move those to a local backup only at some point.&lt;/p&gt;&#xA;&lt;h2 id=&#34;scheduling&#34;&gt;Scheduling&lt;/h2&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a bunch of stuff to schedule in TrueNAS.&lt;/p&gt;&#xA;&lt;p&gt;First the cloud sync / backup. I have one for my general files and another for all the app configuration stuff, which is all set up in a different data set on the NAS. These will both get synced to pCloud daily. Eventually something more robust as described as above.&lt;/p&gt;&#xA;&lt;p&gt;Then there are S.M.A.R.T. tests for all the disks. I have those scheduled for once a week.&lt;/p&gt;&#xA;&lt;p&gt;Finally there is a scrub task, which is a ZFS thing that I only partially understand. But I know that it examines all your stored data along with the checksums to see if anything has been corrupted. If it finds anything, it can generally correct it. So that&amp;rsquo;s nice. The schedule on that is a bit more complex based on what was set up by default. I&amp;rsquo;m not messing with that until I understand more.&lt;/p&gt;&#xA;&lt;p&gt;I set up the timing on all that so each of these things should not be happening while another major taks is happening. I think that&amp;rsquo;s sensible. You don&amp;rsquo;t want to be syncing data while your checking your disks or data. I guess.&lt;/p&gt;&#xA;&lt;h2 id=&#34;monitoring&#34;&gt;Monitoring&lt;/h2&gt;&#xA;&lt;p&gt;One thing I was proud of in my previous server setups was the monitoring I set up. Every service and app was checked via cronjobs. If it was running and OK, I&amp;rsquo;d ping a check I set up on &lt;a href=&#34;https://healthchecks.io/&#34;&gt;https://healthchecks.io/&lt;/a&gt;. If it didn&amp;rsquo;t get a ping in a certain interval it would start emailing me.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s already some alerting built in to TrueNAS. If disks go down or have errors, if S.M.A.R.T. tests fail, and a few other things, I set up alerting to notify me by email.&lt;/p&gt;&#xA;&lt;p&gt;But what if TrueNAS itself goes down? Power or internet goes out? For that I set up HealthChecks again with a very simple cronjob that pings every so often. If it doesn&amp;rsquo;t ping, I know about it.&lt;/p&gt;&#xA;&lt;p&gt;I thought about doing the same for my other apps, but wound up installing Uptime Kuma. This is kind of the opposite idea. HealthChecks is like a &amp;ldquo;dead man&amp;rsquo;s drop&amp;rdquo; - it doesn&amp;rsquo;t get a ping, it takes action.&lt;/p&gt;&#xA;&lt;p&gt;Uptime Kuma, on the other hand, pings each of my services regularly, and if that ping fails, it notifies me by email. Here&amp;rsquo;s my dashboard:&lt;/p&gt;&#xA;&lt;img alt=&#34;uptime kuma dashboard&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-07-20/kuma.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;et-cetera&#34;&gt;Et cetera&lt;/h2&gt;&#xA;&lt;p&gt;Just realizing I could go on for a while more, but that&amp;rsquo;s enough for one post. There will be more, probably, later.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Simple Image Sequence Viewer with mpv</title>
      <link>https://bit-101.com/blog/posts/2025-06-28/simple-image-sequence-viewer/</link>
      <pubDate>Sat, 28 Jun 2025 10:54:35 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-06-28/simple-image-sequence-viewer/</guid>
      <description>&lt;p&gt;A while back I created an app to allow myself to play a sequence of images as a video. I called it &lt;code&gt;isv&lt;/code&gt; for &amp;ldquo;Image Sequence Viewer&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-06-28/image-sequence-viewr-isv/&#34;&gt;https://bit-101.com/blog/posts/2024-06-28/image-sequence-viewr-isv/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;It was decent but it had issues so I kind of stopped using it. But I still have a need for the use case. I&amp;rsquo;m often rendering an animation as a long sequence of png frames and then creating a video from them using ffmpeg. I don&amp;rsquo;t want to have to wait until the whole thing is done before seeing how it&amp;rsquo;s coming along, so I go into the folder where the frames are being created and view the first one and use the arrow keys to scroll through them in a kind of janky animation.&lt;/p&gt;&#xA;&lt;p&gt;This morning, on a whim, I decided to see if &lt;code&gt;mpv&lt;/code&gt;, the video player I use while developing animations, could play image sequences natively. Lo and behold! It can! The syntax is somewhat complex. It does this by merging the files into a video on the fly, so there are a few &lt;code&gt;mf&lt;/code&gt; or merge file commands. Found a workable solution on StackOverflow and adapted that into a shell script that you can pass in a directory path to.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#!/bin/sh&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mpv mf://&lt;span style=&#34;color:#033&#34;&gt;$1&lt;/span&gt;/*.png -mf-fps &lt;span style=&#34;color:#f60&#34;&gt;30&lt;/span&gt; --loop 2&amp;gt;/dev/null&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Some explanation:&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;mf://&lt;/code&gt; is the merge file path of the files to merge. There are several formats you can use here, but I&amp;rsquo;m just using the glob version. &lt;code&gt;$1&lt;/code&gt; is the first argument to the script, which should be the path to the frames. You can also pass a file containing a list of the frames, or a &lt;code&gt;printf&lt;/code&gt; type statement like &lt;code&gt;image-%d.png&lt;/code&gt;. The glob is simple and works.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;-mf-fps&lt;/code&gt; is the frame rate and &lt;code&gt;--loop&lt;/code&gt; makes the resulting animation play continuously. Quiet things down by sending any output to &lt;code&gt;/dev/null&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I put this in a script file called &lt;code&gt;mpf&lt;/code&gt; so I just need to say &lt;code&gt;mpf out/frames&lt;/code&gt; and I have a looping video playing with whatever frames have been rendered so far.&lt;/p&gt;&#xA;&lt;p&gt;It doesn&amp;rsquo;t have all the bells and whistles that I tried to put in &lt;code&gt;isv&lt;/code&gt;, like bouncing forwards and backwards, or watching for new frames and playing them. Here I&amp;rsquo;d need to stop and run the script again to get new frames. But it works great and is very simple. Maybe I&amp;rsquo;ll see if I can get some kind of polling going, but I assume that&amp;rsquo;s going to mean stopping the video and restarting it. Not quite as seemless as &lt;code&gt;isv&lt;/code&gt; was on that front. On the other hand, this winds up being an actual video that I can resize, even full screen, which I couldn&amp;rsquo;t do in &lt;code&gt;isv&lt;/code&gt; and I can still stop, start, go back and forth frame by frame, etc.&lt;/p&gt;&#xA;&lt;p&gt;For a one line script&amp;hellip; not bad.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>POV-Ray</title>
      <link>https://bit-101.com/blog/posts/2025-06-14/pov-ray/</link>
      <pubDate>Sat, 14 Jun 2025 12:03:53 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-06-14/pov-ray/</guid>
      <description>&lt;p&gt;Let&amp;rsquo;s do 3D for a while!&lt;/p&gt;&#xA;&lt;p&gt;In the past few weeks, I&amp;rsquo;ve taken a break from making music. I&amp;rsquo;m sure I&amp;rsquo;ll get back to it, but I was doing it just about every day for five plus months and I needed to step away. I&amp;rsquo;ve been thinking about dipping my toes in 3D, so why not follow that path for a bit?&lt;/p&gt;&#xA;&lt;p&gt;I installed Blender and rendered a gray cube, then got overwhelmed by the UI. There&amp;rsquo;s all kinds of menus and all kinds of panels. And the panels have menus, and the menus have panels. And I don&amp;rsquo;t know what most of it means. I&amp;rsquo;m sure I could learn, but it just wasn&amp;rsquo;t calling to me. I&amp;rsquo;m not a big UI fan. For coding, I don&amp;rsquo;t like fancy UIs or graphical text editors. I use neovim. For graphics, I don&amp;rsquo;t use PhotoShop or any other paint or vector graphics program. I create graphics in code, in neovim. For animations, I don&amp;rsquo;t use AfterEffects or any other animation package. I create frames in code and then create movies and gifs with ffmpeg - in neovim. For music, I am not a fan of DAWs (Digital Audio Workstations) like Ableton or Reason or whatever. I like Supercollider (which I code in neovim) and trackers, and hardware synths and sequencers.&lt;/p&gt;&#xA;&lt;p&gt;So for 3D, there was an obvious solution&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;pov-ray&#34;&gt;POV-Ray&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.povray.org/&#34;&gt;POV-Ray&lt;/a&gt; (persistence of vision raytracer) is a 3D raytracing program that has been around for decades. I&amp;rsquo;m probably just going to call it &amp;ldquo;povray&amp;rdquo; or &amp;ldquo;pov&amp;rdquo; for the rest of this article. While it does have its own IDE on some platforms, you can also just write a &lt;code&gt;.pov&lt;/code&gt; scene file in plain text and render it with the povray command line tool. Guess which editor I&amp;rsquo;m writing my &lt;code&gt;.pov&lt;/code&gt; files in!&lt;/p&gt;&#xA;&lt;p&gt;POV-Ray goes deep. Deeper than I expected when I started. There&amp;rsquo;s a lot to learn, but unlike Blender, it&amp;rsquo;s not all in your face, intimidating you. You can just write a simple scene and render it and keep adding things to it as you learn more. I know, I know, you can do that in Blender too. But when all those features are in my face, it&amp;rsquo;s distracting.&lt;/p&gt;&#xA;&lt;p&gt;The purpose of this post isn&amp;rsquo;t to teach you povray. There are plenty of tutorials and videos going back many years on that. But I just wanted to walk through some modern solutions to rendering things with povray and the path I took. Maybe you&amp;rsquo;ll find it useful. I&amp;rsquo;ll also throw in some random videos I&amp;rsquo;ve created with pov recently. They won&amp;rsquo;t necessarily be related to the topics I&amp;rsquo;m discussing in the text at that point, but they should break things up and prevent a huge wall of text and code. Well&amp;hellip; here&amp;rsquo;s one now!&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;Random mirrored shapes rotating in 3d&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-06-14/video_01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Here&amp;rsquo;s a pretty simple scene file that renders a box sitting on a plane, with a sky-like background, some lights and a camera.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-povray&#34; data-lang=&#34;povray&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#version&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;3.7&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;stdinc.inc&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;stones.inc&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;global_settings&lt;/span&gt; { &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;assumed_gamma&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;light_source&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  White&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;light_source&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f60&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  White &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;camera&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;right&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; image_width &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; image_height&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;location&lt;/span&gt; &amp;lt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;look_at&lt;/span&gt;  &amp;lt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;box&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;texture&lt;/span&gt; { T_Stone10 }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;plane&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;&amp;gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;texture&lt;/span&gt; { T_Stone40 }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;sky_sphere&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;pigment&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;gradient&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;y&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;color_map&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      [&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;color&lt;/span&gt; White]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      [&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;color&lt;/span&gt; Blue]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;scale&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;translate&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yeah, it&amp;rsquo;s not nothing, but each piece is relatively straightforward. The light sources have a position and a color (there are more complex light sources, but these work fine). The camera has a location and a point it&amp;rsquo;s looking at. It also has that first line which sets up the aspect ratio of the camera. That one is boilerplate. The box is made from two points and a texture. The plane has a vector showing it&amp;rsquo;s orientation: 1 on the y axis makes it extend out on the x and z axes. And it has a position on the y axis, -1 puts it right under the box. The sky sphere is a bit more complex, but you can see it makes a white/blue gradient on the y axis. And here&amp;rsquo;s what you get:&lt;/p&gt;&#xA;&lt;img alt=&#34;a box on a plane&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-06-14/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;It uses some built-in textures that are included at the top of the file.&lt;/p&gt;&#xA;&lt;h2 id=&#34;can-i-make-it-procedural&#34;&gt;Can I make it procedural?&lt;/h2&gt;&#xA;&lt;p&gt;When I say procedural here, I don&amp;rsquo;t mean &amp;ldquo;generative&amp;rdquo; or &amp;ldquo;algorithmic&amp;rdquo;, aka &amp;ldquo;random&amp;rdquo;. I mean the difference between procedural code where you tell the computer what to do step by step, and declarative code, where you just describe what you want to have happen. POV-Ray scene files are declarative. You describe the scene and let pov figure out how to create it. As opposed to saying, for example&amp;hellip;&lt;/p&gt;&#xA;&lt;ol start=&#34;0&#34;&gt;&#xA;&lt;li&gt;Create a new scene.&lt;/li&gt;&#xA;&lt;li&gt;Create a 2x2x2 box at 0, 0, 0.&lt;/li&gt;&#xA;&lt;li&gt;Give the box a stone texture.&lt;/li&gt;&#xA;&lt;li&gt;Add the box to the scene.&lt;/li&gt;&#xA;&lt;li&gt;Create a plane on the x-z axes, at -1 y.&lt;/li&gt;&#xA;&lt;li&gt;Give the plane a different stone texture.&lt;/li&gt;&#xA;&lt;li&gt;Add the plane to the scene.&lt;/li&gt;&#xA;&lt;li&gt;Create a light source at &amp;hellip; etc.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This might look like in pseudocode:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;scene &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;new&lt;/span&gt; Scene()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;box &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;new&lt;/span&gt; Box(&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;box.position(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;box.setTexture(Stone10)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;scene.add(box)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Random video alert!&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;Random mirrored shapes rotating in 3d&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-06-14/video_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;So, yeah, I set out to do something like that using Go. And I got pretty far. You can see my progress here:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/bit101/pov&#34;&gt;https://github.com/bit101/pov&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;In brief, the scene has an array of objects. You can create various objects and assign them different properties and when you are done, it will serialize the scene and all its objects into a &lt;code&gt;.pov&lt;/code&gt; file that you can then render with povray. In fact, the program even took care of running povray for you and displaying the image.&lt;/p&gt;&#xA;&lt;p&gt;Say you have a sphere object with x, y, z and radius properties. This is a struct in Go. It get serialized as:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-povray&#34; data-lang=&#34;povray&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;sphere&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;&amp;gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Of course, it uses the struct properties to fill in the text that&amp;rsquo;s written to the &lt;code&gt;.pov&lt;/code&gt; file. It mostly does that with &lt;code&gt;sprintf&lt;/code&gt; statements sort of like &lt;code&gt;sprintf(&amp;quot;&amp;lt;%f, %f, %f&amp;gt; %f&amp;quot;, sphere.x, sphere.y, sphere.z, sphere.radius)&lt;/code&gt;. Pretty simple stuff.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;Aizawa strange attractor&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-06-14/video_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h3 id=&#34;where-it-started-getting-complex&#34;&gt;Where it started getting complex&lt;/h3&gt;&#xA;&lt;h4 id=&#34;first-transforms&#34;&gt;First, transforms.&lt;/h4&gt;&#xA;&lt;p&gt;Transforms can be used on objects or textures, patterns, etc. You can say, for example, &lt;code&gt;translate &amp;lt;1, 2, 3&amp;gt;&lt;/code&gt; or &lt;code&gt;scale &amp;lt;2, 3, 4&amp;gt;&lt;/code&gt; or &lt;code&gt;rotate &amp;lt;10, 20, 30&amp;gt;&lt;/code&gt; to transform on separate axes.&lt;/p&gt;&#xA;&lt;p&gt;But you can also transform on all axes with one value: &lt;code&gt;scale 5&lt;/code&gt;. Or you can transform on only one axis like: &lt;code&gt;scale 2*y&lt;/code&gt; - scales by 2 on the y axis only.&lt;/p&gt;&#xA;&lt;p&gt;So now there are three different transform syntaxes I had to account for. Not impossible. I started down that path, but it was getting a bit messy.&lt;/p&gt;&#xA;&lt;h4 id=&#34;second-textures-etc&#34;&gt;Second, textures, etc.&lt;/h4&gt;&#xA;&lt;p&gt;An object can have a texture, a material, a pigment, a normal, and a finish, which combine to define how the object looks. In fact, it can have more than one of all these things. And you can combine these in different ways. For example, you can add a pigment directly to an object, or you can put it inside a texture. And you can have pigments and textures that are predefined, like &lt;code&gt;T_Stone10&lt;/code&gt; and &lt;code&gt;Blue&lt;/code&gt; used above, or you can have colors defined with &lt;code&gt;rgb &amp;lt;r, g, b&amp;gt;&lt;/code&gt; and manually create textures on a lower level.&lt;/p&gt;&#xA;&lt;p&gt;In short, a lot of permutations to account for in code. I did not go very far down that path.&lt;/p&gt;&#xA;&lt;h4 id=&#34;third-animation&#34;&gt;Third, animation.&lt;/h4&gt;&#xA;&lt;p&gt;To animate in povray, you tell it how many frames you want to render. Let&amp;rsquo;s say you want to render 60 frames. Now in your &lt;code&gt;.pov&lt;/code&gt; file you have access to a variable called &lt;code&gt;clock&lt;/code&gt;, which will be 0.0 on the first frame and 1.0 on the last frame. So you can rotate an object around the y axis like: &lt;code&gt;rotate 360*y*clock&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;I was happy to see this because it&amp;rsquo;s very similar to how I animate in my own 2D rendering library. The render function gets passed a &lt;code&gt;percent&lt;/code&gt; value that ranges from 0.0 to 1.0 depending on what frame it&amp;rsquo;s rendering. I can use that in all kinds of ways to make single or smoothly looping animations.&lt;/p&gt;&#xA;&lt;p&gt;But I struggled to find a good way to represent this &lt;code&gt;clock&lt;/code&gt; variable in my Go code. With all other values, I could use whatever expressions I wanted and distill them down to a final serialized value. With animation, I needed to preserve the expression, &lt;code&gt;360*y*clock&lt;/code&gt; and serialize that.&lt;/p&gt;&#xA;&lt;p&gt;The solution for the &lt;code&gt;clock&lt;/code&gt; and increasingly for many of the other issues I was running into, was just to save everything as strings. And eventually the absurdity of what I was doing hit me. I was using an external program to store a bunch of strings that I wrote to a text file. Why not just write the text file directly???&lt;/p&gt;&#xA;&lt;p&gt;So that began phase two.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;Aizawa strange attractor&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-06-14/video_04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h2 id=&#34;going-native-and-creating-a-build-system&#34;&gt;Going native and creating a build system&lt;/h2&gt;&#xA;&lt;p&gt;So I switched my workflow to just writing &lt;code&gt;.pov&lt;/code&gt; files. This was great. When I learned a new object or property I didn&amp;rsquo;t have to create a new object to represent it and figure out how to serialize it. I could just write it.&lt;/p&gt;&#xA;&lt;p&gt;At this point I started diving into creating a great build system. I like using &lt;code&gt;make&lt;/code&gt; and I have &lt;code&gt;F5&lt;/code&gt; set up in neovim to just run &lt;code&gt;make&lt;/code&gt; which will look for a &lt;code&gt;Makefile&lt;/code&gt; in the current directory and execute it.&lt;/p&gt;&#xA;&lt;p&gt;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;Red and yellow turbulent ball&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-06-14/video_05.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;Here&amp;rsquo;s the &lt;code&gt;Makefile&lt;/code&gt; I&amp;rsquo;m currently using:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-make&#34; data-lang=&#34;make&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;FRAMES&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# ZEROS = number of digits in FRAMES&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;ZEROS&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;CYCLIC&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;WIDTH&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;HEIGHT&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;600&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;VERBOSE&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;CONSOLE&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;ANTIALIAS&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#033&#34;&gt;FPS&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;30&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# image or video&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; image&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# if you change a variable above and want to force a re-render&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# default: clean image&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;################&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# Single frame&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;################&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;image&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; out/out.png&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@eog ./out/out.png 2&amp;amp;&amp;gt; /dev/null&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;out/out.png&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; main.pov&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@echo &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;building image...&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@rm -rf out&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@mkdir -p out&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@povray &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;main.pov &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Antialias&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;ANTIALIAS&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Display&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;false&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Verbose&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;VERBOSE&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;All_Console&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;CONSOLE&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;+W&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;WIDTH&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; +H&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;HEIGHT&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Output_File_Name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;./out/out.png&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;################&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# Animation&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;################&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;video&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; out/out.mp4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@mpv -loop ./out/out.mp4 2&amp;amp;&amp;gt; /dev/null&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;out/out.mp4&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; out/frames&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@echo &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;generating video...&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@ffmpeg &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;-y &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;-framerate &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;FPS&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;-i ./out/frames/frame_%0&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;ZEROS&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt;d.png &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;-s:v &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;WIDTH&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt;x&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;HEIGHT&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;-c:v libx264 &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;-profile:v high &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;-crf &lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;-pix_fmt yuv420p &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;./out/out.mp4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;out/frames&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; main.pov &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@echo &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;generating frames...&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@rm -rf out&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@mkdir -p out/frames&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@povray &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;main.pov &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Antialias&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;ANTIALIAS&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Display&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;false&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;+W&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;WIDTH&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; +H&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;HEIGHT&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Final_Frame&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;FRAMES&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Cyclic_Animation&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;CYCLIC&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Verbose&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;VERBOSE&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;All_Console&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;CONSOLE&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#033&#34;&gt;Output_File_Name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;./out/frames/frame_.png&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;################&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# Other&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;################&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;clean&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@echo &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;cleaning...&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;@rm -rf out&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I won&amp;rsquo;t go into this in too much depth, but will point out some things I am proud of.&lt;/p&gt;&#xA;&lt;p&gt;Up top are all the variables and settings that get incorporated in the rest of the build.&lt;/p&gt;&#xA;&lt;p&gt;Running &lt;code&gt;make&lt;/code&gt; will execute the first target, &lt;code&gt;default&lt;/code&gt;. You can set either &lt;code&gt;image&lt;/code&gt; or &lt;code&gt;video&lt;/code&gt; as a prerequisite here and it will run either of those targets.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;image&lt;/code&gt; target will check &lt;code&gt;out/out.png&lt;/code&gt;. This will check &lt;code&gt;main.pov&lt;/code&gt;. If that &lt;code&gt;main.pov&lt;/code&gt; is newer than &lt;code&gt;out/out.png&lt;/code&gt; or if the png doesn&amp;rsquo;t exist, it will run that target and build the image. The &lt;code&gt;image&lt;/code&gt; target will the display the image.&lt;/p&gt;&#xA;&lt;p&gt;Similarly, the &lt;code&gt;video&lt;/code&gt; target will check &lt;code&gt;out/out.mp4&lt;/code&gt; which will check the date of the &lt;code&gt;out/frames&lt;/code&gt; directory to see if it needs to re-render the video from the frames, and will check &lt;code&gt;main.pov&lt;/code&gt; to see if it needs to regenerate those frames.&lt;/p&gt;&#xA;&lt;p&gt;This means that if you just spent 10 minutes rendering a video and run &lt;code&gt;make&lt;/code&gt; again when nothing has changed, it&amp;rsquo;s not going to start from scratch. It knows that everything is up to date and will just display the existing video.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;Eight blobby spheres&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-06-14/video_06.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h2 id=&#34;a-little-from-column-a-and-some-from-column-b&#34;&gt;A little from column A and some from column B&lt;/h2&gt;&#xA;&lt;p&gt;This was all working great until I started doing some more complex renders. I started algorithmically generating lots of objects using various formulas. This requires things like variables and functions. POV-Ray has these things&amp;hellip; kind of.&lt;/p&gt;&#xA;&lt;p&gt;&amp;ldquo;Variables&amp;rdquo; are defined with &lt;code&gt;#declare&lt;/code&gt; statements (or &lt;code&gt;#local&lt;/code&gt; statements). But if you want to change the value of a variable you have to redeclare it. Like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-povray&#34; data-lang=&#34;povray&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; X &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; X &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; X &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;re supposed to use capital letters to start variables in most places. And in spite of the fact that most povray code does not need semicolons, &lt;code&gt;#declare&lt;/code&gt; and &lt;code&gt;#local&lt;/code&gt; statements do.&lt;/p&gt;&#xA;&lt;p&gt;There are two function-like structures: functions and macros. Functions are also declared and can have parameters (floats only) and can return a single value (float only).&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-povray&#34; data-lang=&#34;povray&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; Foo &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; function(n) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  n &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; Bar &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; Foo(&lt;span style=&#34;color:#f60&#34;&gt;7&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// Bar will now equal 14&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Functions can take vectors (&lt;code&gt;&amp;lt;x, y, z&amp;gt;&lt;/code&gt; values) but can only be used in specific transform type cases. Otherwise, if you want to affect more than one value, you&amp;rsquo;d have to use a macro and nest &lt;code&gt;#declare&lt;/code&gt; statements inside your function.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-povray&#34; data-lang=&#34;povray&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#macro&lt;/span&gt; Multiply2(X, Y, Z)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; X &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; X &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; Y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; Y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; Z &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; Z &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#end&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; X &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; Y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#declare&lt;/span&gt; Z &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Multiply(X, Y, Z);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// X, Y, Z will now equal 2, 4, 6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that functions are defined with brackets &lt;code&gt;{ }&lt;/code&gt; while macros use &lt;code&gt;#macro&lt;/code&gt; and &lt;code&gt;#end&lt;/code&gt;. Also, function parameters should be lower case while macro params should be upper case. Odd syntax stuff like this makes variables, functions and macros rather painful.&lt;/p&gt;&#xA;&lt;p&gt;Also note that I&amp;rsquo;m hardly an expert here. This stuff is a bit confusing and old fashioned, so I may have gotten some things wrong or overcomplicated it. I&amp;rsquo;m happy to be corrected if so.&lt;/p&gt;&#xA;&lt;p&gt;So I wound up taking a hybrid approach. If I need to create a complex object that requires a lot of math, functions, recursion, etc., I&amp;rsquo;ve taken to building that in Go and just outputting a file that defines the object itself. I can then import that object into my scene file and instantiate it, transform it, add textures, materials, etc.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not going to deep dive into that here, but if you want to take a look at an example, you can see &lt;a href=&#34;https://github.com/bit101/pov_experiments/tree/main/250613b&#34;&gt;https://github.com/bit101/pov_experiments/tree/main/250613b&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;And here&amp;rsquo;s the result of that:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;Fractal shape made of spheres&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-06-14/video_07.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I don&amp;rsquo;t know how the hell I would have done something that complex in povray only.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;I always feel like I have to have a summary rather than just ending off on that last thought. But I&amp;rsquo;m not always sure what to summarize. Maybe I&amp;rsquo;m just showing off. Or documenting this so when I come back to it in a few years I can be like, &amp;ldquo;oh yeah, that&amp;rsquo;s how I handled that&amp;hellip;&amp;rdquo;. Anyway, maybe you&amp;rsquo;ll see more content along these lines. Maybe I&amp;rsquo;ll ricochet off in some other direction next week. One way or the other, I&amp;rsquo;ll be back.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Robins</title>
      <link>https://bit-101.com/blog/posts/2025-05-26/robins/</link>
      <pubDate>Mon, 26 May 2025 10:19:18 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-05-26/robins/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;A few years ago I build a wood shed in my back yard. it&amp;rsquo;s just an open structure to stack logs in, with a roof to keep out the rain. I made a shelf at the top for storing sticks for kindling. I walk past this shed several times a day - whenever I leave the house or come home.&lt;/p&gt;&#xA;&lt;h2 id=&#34;first-sighting&#34;&gt;First sighting&lt;/h2&gt;&#xA;&lt;img alt=&#34;a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_01.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;In late April, I was coming back from a dog walk and a bird flew out of the top of the shed. When I looked closer, I saw a bird&amp;rsquo;s nest in amongst the kindling sticks. The bird was an American robin. Note, these are very different birds from what gets called a robin in the UK and other areas (&lt;a href=&#34;https://en.wikipedia.org/wiki/European_robin&#34;&gt;the European robin&lt;/a&gt;). I&amp;rsquo;d often read about how cute and fluffy robins are and it never made sense to me. American robins are stern looking, business-like birds. Not ugly, but they always look very serious and slightly annoyed. Eventually I discovered that these are not the same robins others were talking about. Anyway, cool! A nest right there at eye level. I did some reading to find out what was going on there.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-i-read&#34;&gt;What I read&lt;/h2&gt;&#xA;&lt;p&gt;Some facts about robins:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Robins couples are monogomous for a single mating season. The next year they choose new partners.&lt;/li&gt;&#xA;&lt;li&gt;They don&amp;rsquo;t live in nests. They sleep and rest in trees, on branches. The nests are solely for laying and hatching eggs and raising the babies.&lt;/li&gt;&#xA;&lt;li&gt;The female robin does most of the nest building, though the male often helps gathering materials - grass, string, mud.&lt;/li&gt;&#xA;&lt;li&gt;Once the nest is built, the mother moves in and stays there for the cycle of one batch of young.&lt;/li&gt;&#xA;&lt;li&gt;The male stays nearby and keeps an eye on things, defending the nest as much as he is able.&lt;/li&gt;&#xA;&lt;li&gt;The mother robin will lay anywhere from two to seven eggs, three or four being most common.&lt;/li&gt;&#xA;&lt;li&gt;The eggs hatch after 12-14 days, at which point, the mother will spend most of her time gathering food (worms, bugs) and feeding them. The father might help get food for them now and then as well.&lt;/li&gt;&#xA;&lt;li&gt;About two weeks after hatching, the babies will be ready to start flying. Soon after that, they are off on their own.&lt;/li&gt;&#xA;&lt;li&gt;The parents then abandon that nest and build a new one for the next round of babies. They may have as many as three or four nests in a season.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;what-i-saw&#34;&gt;What I saw&lt;/h2&gt;&#xA;&lt;img alt=&#34;a robin in a nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_02.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;From there on, every day I would see the mother sitting on the nest. She&amp;rsquo;d generally fly away as I walked by, which I felt bad about. But that was how I left my house and came back. After a while she did start to get somewhat used to us and if we moved slowly and passed by as far from the shed as we could get, she would often stay in the nest and eye us carefully.&lt;/p&gt;&#xA;&lt;p&gt;After a week or so, eggs!&lt;/p&gt;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_03.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Three tiny, perfect blue eggs.&lt;/p&gt;&#xA;&lt;p&gt;Some notes on the photos:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Some of the photos, like the above, are very close up and taken from right above the nest. I never purposely scared the mother away. I only approached if she was not there. I held my phone up, took a photo and was out of there in two seconds. I did not touch the nest, its contents, or its surroundings.&lt;/li&gt;&#xA;&lt;li&gt;All other photos were taken at high zoom from at least 20 feet away, sometimes from the yard, sometimes from a window.&lt;/li&gt;&#xA;&lt;li&gt;In some of the photos the inside of the nest looks very red. That&amp;rsquo;s just a reflection of the red stain on the shed itself, where the natural light did not reach. It&amp;rsquo;s not blood!&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;After the eggs, the mother was almost always sitting on the nest. I imagine the did the bare minimum foraging to feed herself, but otherwise she was right there.&lt;/p&gt;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_04.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Right on schedule, about two weeks after I saw the first eggs, they hatched. At first we just noticed that the mother was not always sitting down in the nest, but sitting higher or on the edge of the nest.&lt;/p&gt;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_05.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Eventually I discovered three very tiny, ugly, pink, fuzzy creatures.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_06.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_07.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_08.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_09.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;And the feeding began. At first, mother robin was still mostly sitting on the nest, but more and more often she&amp;rsquo;d be away getting food.&lt;/p&gt;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_10.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Within a week, they went from being amorphous lumps to looking like birds. Very hungry birds.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_11.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_12.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Sometimes when they were sated, they even started showing some curiosity about the world outside the nest.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_13.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_14.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;In the second week after hatching, I was shocked at how much they had grown.&lt;/p&gt;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_15.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;At this point, the mother was at the nest about half the time at best. She&amp;rsquo;d appear, spend a few minutes shoving food down her babies throats and fly off to get more.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_16.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_17.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;And at this point I started seeing the father more often. One time while I was taking zoomed in photos from a good 20-25 feet away, he swooped down, landed on the ground right in front of me, scolded me loudly and flew away. Go Dad!&lt;/p&gt;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_18.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This was the last photo I got of them. They barely fit in the nest after two full weeks of growing. Then over the course of a day or two, we saw the babies fly out of the nest, one by one. Both parents were right there watching over them and screaming loudly at anything in the vicinity.&lt;/p&gt;&#xA;&lt;img alt=&#34;three blue egges in a robins nest&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robins_19.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;And then&amp;hellip; literal empty nest. I can&amp;rsquo;t say I was sad exactly. But I missed looking out for them every time I went outside. The nest was abandoned and the wood shed was just a wood shed. The abandonment was just so sudden though. I thought they&amp;rsquo;d do some test flights for a few days or so, but still come back to the nest. Nope. They were gone and never returned.&lt;/p&gt;&#xA;&lt;p&gt;I do wonder if they are ok. All three hatched, grew up perfectly and flew off on their own, so I&amp;rsquo;m assuming they are just getting on with their lives.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, it was a magical month. I&amp;rsquo;ll never look at robins the same. And every time I see one in the neighborhood, I&amp;rsquo;ll wonder if it&amp;rsquo;s one of &amp;ldquo;mine&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;I leave you with a bonus video of feeding time.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;robin feeding her young&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-05-26/robin_feeding.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;</description>
    </item>
    <item>
      <title>Yamaha Seqtrak</title>
      <link>https://bit-101.com/blog/posts/2025-05-12/yamaha-seqtrak/</link>
      <pubDate>Sun, 11 May 2025 05:15:24 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-05-12/yamaha-seqtrak/</guid>
      <description>&lt;p&gt;Last week I got a new musical toy - the Yamaha Seqtrak. In this article, I&amp;rsquo;ll share my feelings about it, along with some unavoidable comparisons to my favorite child, the Dirtywave M8.&lt;/p&gt;&#xA;&lt;h2 id=&#34;but-first-some-background&#34;&gt;But first, some background&amp;hellip;&lt;/h2&gt;&#xA;&lt;p&gt;A few weeks ago I cashed in on a few rewards programs and had some unbudgeted cash to spend. There was no question what I&amp;rsquo;d spend it on - something synthy. Initially I got a Sonicware Liven Texture Lab. This is a granular synthesis device. I&amp;rsquo;m going to assume you either know what that is, don&amp;rsquo;t care, or will figure it out on your own. Anyway, I was interested in granular, but wasn&amp;rsquo;t too impressed with this machine, so I returned it.&lt;/p&gt;&#xA;&lt;img alt=&#34;Sonicware Texture Lab&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/texturelab.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Next on my list was a 1010Music Lemondrop - also a granular synthesizer, but seems to be a far more capable device. But it was going to cost significantly more. I wasn&amp;rsquo;t sure I wanted another single purpose synth at this point. I started leaning towards some other more flexible device that can make full songs - like a groove box.&lt;/p&gt;&#xA;&lt;img alt=&#34;1010 Lemondrop&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/lemondrop.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;The Lemondrop is still on my list, but for now I decided on the Seqtrak. It arrived last week and I&amp;rsquo;ve been having tons of fun with it. It&amp;rsquo;s a keeper.&lt;/p&gt;&#xA;&lt;img alt=&#34;Yamaha Seqtrak&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/seqtrak.jpg&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;what-is-it&#34;&gt;What is it?&lt;/h2&gt;&#xA;&lt;p&gt;The Seqtrak is a 3S machine (I just coined that) - Sequencer, Sampler, Synthesizer - just like the Dirtwave M8.&lt;/p&gt;&#xA;&lt;p&gt;It has eleven instruments:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;7 drum / percussion instruments&lt;/li&gt;&#xA;&lt;li&gt;2 AWM2 (advanced wave memory 2 - I still need to figure out what that even is) synthesizers&lt;/li&gt;&#xA;&lt;li&gt;1 FM synthesizer based on the classic Yamaha DX&lt;/li&gt;&#xA;&lt;li&gt;1 sampler&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;img alt=&#34;instrument knobs&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/instruments.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Those eleven instruments come with literally thousands of presets and samples - and lots of space (500MB) to add more by downloading free sounds and packs from Yamaha, or importing or recording your own samples. The presets and overall sound design capabilities on this device are amazing. So many wonderful sounds even without tweaking them too much.&lt;/p&gt;&#xA;&lt;p&gt;It also contains a pretty powerful sequencer where you can program in up to 128 steps per track. Each of the eleven instruments can have six different tracks. There&amp;rsquo;s a scene mode where you can play different combinations of tracks from the different instruments, and a song mode where you can sequence up to sixteen scenes to play back in series.&lt;/p&gt;&#xA;&lt;img alt=&#34;sequencer keys&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/sequencer.jpg&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;the-good&#34;&gt;The good&lt;/h2&gt;&#xA;&lt;p&gt;All of the above is good. In general, it&amp;rsquo;s a fun machine to create stuff that just sounds good. It&amp;rsquo;s got mono, poly, and chord modes. For the chords, you choose the key and scale type and it picks the &amp;ldquo;right&amp;rdquo; chords for you (though you can also edit them). And it limits you to the keys of that scale, so it&amp;rsquo;s actually pretty hard to make something that sounds bad. But you can switch into a keyboard mode - or use an external midi keyboard, bypassing the limitations.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s powered by an internal rechargeable battery. The life seems decent and it charges via USB-C. So it&amp;rsquo;s super portable.&lt;/p&gt;&#xA;&lt;p&gt;While the interface is pretty sparse (see next section), this device goes very deep with advanced actions and effects you don&amp;rsquo;t see on other similar devices.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-kind-of-weird&#34;&gt;The kind of weird&lt;/h2&gt;&#xA;&lt;p&gt;Synths and related devices often get criticized for &amp;ldquo;menu diving&amp;rdquo; - where you have to use a button to activate a menu in a tiny display, then twist a knob to choose an option in that menu, then hit a button, then twist a knob, sometimes several levels deep. None of that here, because there isn&amp;rsquo;t even a display!&lt;/p&gt;&#xA;&lt;p&gt;Instead, there is a huge reliance on arcane key combos. Hold this button and press this other one, hold this button and twist that knob. In some cases, you have to hold two buttons and hit a third. Like a mini game of twister. &lt;del&gt;All&lt;/del&gt; Most of the knobs and buttons are labeled, but sometimes none of the controls you are combining are logically connected to the action you are trying to take. For example, to activate Bluetooth you press and hold the &amp;ldquo;All&amp;rdquo; knob and tap the &amp;ldquo;Swing&amp;rdquo; button - of course! So there&amp;rsquo;s memorization required.&lt;/p&gt;&#xA;&lt;p&gt;As a sort of menu, there is a list of back-lit items next two four knobs. Depending on what mode you are in, up to four of these items will light up, indicating that the four knobs will control these four parameters, e.g. attack, decay, filter, resonance. But there are more actions than there are back-lit items, so sometimes you just get the &amp;ldquo;other&amp;rdquo; item and you have to just know what the knobs are controlling.&lt;/p&gt;&#xA;&lt;img alt=&#34;menu items&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/menu.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;There are also three touch sliders, labled &amp;ldquo;FX Level&amp;rdquo;, &amp;ldquo;High Pass&amp;rdquo; and &amp;ldquo;Repeater&amp;rdquo;. But what they actually do can be totally different than those labels as you go into different modes.&lt;/p&gt;&#xA;&lt;img alt=&#34;sliders&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/sliders.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;And in addition to the 24 &amp;ldquo;keys&amp;rdquo;, 17 knobs and one switch on top of the unit, there are 20 push buttons arranged around all four sides of the device. All labeled on the top.&lt;/p&gt;&#xA;&lt;p&gt;So yeah, a little confusing, and a learning curve for sure. Keep that manual handy.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-app&#34;&gt;The app&lt;/h2&gt;&#xA;&lt;p&gt;I hope I didn&amp;rsquo;t scare you off with the &amp;ldquo;weird&amp;rdquo; section. You do start to remember the combos surprisingly quickly. But there&amp;rsquo;s also an app for that!&lt;/p&gt;&#xA;&lt;img alt=&#34;seqtrak app&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/app.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Actually several apps - Mac, Windows, iOS, Android. The app connects to the device via Bluetooth or USB and gives you the missing display. This makes many of the deeper functions much more usable. In fact, there are a few things you can &lt;em&gt;only&lt;/em&gt; control via the app. The app is also very useful for dialing in exact numbers for settings. Again, since there is no display on the device itself. When you&amp;rsquo;re twisting knobs, there is a stack of eight white LEDs that light up to show you the rough level of what you&amp;rsquo;re adjusting. But in the app, you get the actual number.&lt;/p&gt;&#xA;&lt;img alt=&#34;leds&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/leds.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;As vital as all of that sounds, I find myself mostly using the device without the app. Just adjusting parameters by feel, or by ear, not knowing what the numeric value is. It&amp;rsquo;s kind of liberating. What&amp;rsquo;s the actual BPM here? No idea, but it sounds about right to me.&lt;/p&gt;&#xA;&lt;p&gt;You also need the app for importing Yamaha&amp;rsquo;s presets, samples and packs, and your own samples. And for backing up and restoring your projects, which is important because you can only have eight projects on the device itself. When you use those up, you&amp;rsquo;ll have to back up and delete some to make space.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-not-so-great&#34;&gt;The not so great&lt;/h2&gt;&#xA;&lt;p&gt;While this is a nice device for trying out ideas, experimenting, jamming, etc. Its production features are lacking.&lt;/p&gt;&#xA;&lt;p&gt;There is no concept at all of rendering a finished song, or even stems. You have to hook it up to a recording device or computer via line out or USB and record that way in real time. It would be wonderful to be able to do an internal render to an SD card for example.&lt;/p&gt;&#xA;&lt;p&gt;The eight project limitation is really strange. Especially when they give you 500MB for your own samples. How big can a project be? It should just be a collection of parameters for each step/track/instrument.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m also quite surprised at the song mode limitations. A song can be up to sixteen scenes. Each scene is a set of tracks for each instrument. And each track can be up to eight bars. I did some math. At 120 BPM, that&amp;rsquo;s 0.5 seconds per beat. So a bar (four beats) would be two seconds. Each track/scene can be up to eight bars, so 16 seconds. This means a full song is 16x16 = 256 seconds. Or four minutes and 16 seconds. While that&amp;rsquo;s a decent length for many songs, as a top end limit, that&amp;rsquo;s very constricting. Of course there are hacks you can do with the BPM - like cutting it in half and doubling the beats. But you shouldn&amp;rsquo;t have to be jumping through hoops like that just to get a five-minute song.&lt;/p&gt;&#xA;&lt;p&gt;What you can do is kind of jam with your arrangement as you record. Rather than try to prearrange everything and let it play out, you can use sequence mode to jump around your sequences and manually switch out instrument tracks as you go, or even change parameters on the fly. You can do that all day and make as long a recording as you want. It&amp;rsquo;s just not very repeatable.&lt;/p&gt;&#xA;&lt;p&gt;This probably reflects more on me than on the device. If you just have a bunch of standalone synths and a sequencer, or you do modular, or (OMG) you play the keyboard or real physical instruments, you might be wondering what the fuss is all about. I don&amp;rsquo;t play anything and have a hard time remembering a melody five minutes after I play it. So I&amp;rsquo;m in a place where I need to be able to build little pieces of a song manually and save them, then add to them and iterate on them over time.&lt;/p&gt;&#xA;&lt;p&gt;One more thing. Build-wise, it&amp;rsquo;s pretty light and all plastic. It looks good, but it feels pretty fragile.&lt;/p&gt;&#xA;&lt;h2 id=&#34;comparison-to-m8&#34;&gt;Comparison to M8&lt;/h2&gt;&#xA;&lt;img alt=&#34;the M8 and Seqtrak&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-05-12/compare.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;I knew that this device would be competing for my attention with my beloved Dirtywave M8. They exist in very similar universes - &amp;ldquo;3S devices&amp;rdquo; with fantastic sound design features and tons of effects. But they have completely different workflows, so I figured they would complement each other nicely. I think that assumption has held true. It&amp;rsquo;s kind of like learning a new programming language. The paradigms unique to the new language make you rethink how you code in languages you already know. There&amp;rsquo;s already stuff I&amp;rsquo;ve learned on the Seqtrak that has inspired my M8 work.&lt;/p&gt;&#xA;&lt;p&gt;The Seqtrak is perfect for real time jamming and coming up with ideas. Set a key and scale and start programming in steps and live jamming and recording. You can just bang away and it just makes everything you do sound good.&lt;/p&gt;&#xA;&lt;p&gt;As for producing a finished song though, not so great, as covered above.&lt;/p&gt;&#xA;&lt;p&gt;The M8 on the other hands is more analytical. You&amp;rsquo;re entering steps and parameters in a glorified spreadsheet. You can play live with an external keyboard, but a more common use case is looping a phrase and entering and tweaking notes and effects as you go hearing it over and over and fixing it until it sounds right. So generally not nearly as immediate and spontaneous.&lt;/p&gt;&#xA;&lt;p&gt;But in most other aspects, the M8 shines. Unlimited projects (depending on SD card space), on-device rendering, including stems. There&amp;rsquo;s still a learning curve, but almost everything is right there on the screen. Yeah, there&amp;rsquo;s key shortcuts to learn, but it&amp;rsquo;s mostly select/copy/paste/edit motions that work generally the same on every section of the device. The build quality is superb. You could beat an ogre to death with it. (I&amp;rsquo;ve been playing a lot of Quake.)&lt;/p&gt;&#xA;&lt;p&gt;Another comparison - what you can do per track. The Seqtrak has eleven instruments which can have up to six tracks each. But all those tracks can only play that one instrument - specifically the exact sample/preset you&amp;rsquo;ve chosen for that instrument. You can alter some of the parameters on a step-by-step basis, but you can&amp;rsquo;t choose a whole new sound for a given step or track.&lt;/p&gt;&#xA;&lt;p&gt;On the M8, you only have eight tracks, but every step of every track can play any instrument at any time. Every step of a track could theoretically play a different instrument - out of I think 128 total instruments. So in the end it&amp;rsquo;s far more flexible.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;I think I came off sounding somewhat on the negative side here, but I honestly like this device a lot. It&amp;rsquo;s a load of fun to use and quickly create stuff that sounds very nice. I have absolutely no regrets and I&amp;rsquo;m sure I&amp;rsquo;m going to keep using it regularly. Its core functionality is awesome. I&amp;rsquo;d love to see them make a sturdier model with an actual display of some kind. But that would probably double the price - or more.&lt;/p&gt;&#xA;&lt;p&gt;I suspect I&amp;rsquo;ll be jamming on ideas on the Seqtrak and finishing songs on the M8. Of course, they can also collaborate via MIDI and/or line in/out, so that might be interesting to experiment with as well!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>FM Synth Chords on the M8 Tracker</title>
      <link>https://bit-101.com/blog/posts/2025-04-22/fm-synth-chords-on-m8/</link>
      <pubDate>Tue, 22 Apr 2025 05:45:00 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-04-22/fm-synth-chords-on-m8/</guid>
      <description>&lt;p&gt;I count myself as an M8 noob (and, let&amp;rsquo;s face it, a music noob too), but the best way for me to learn is to write as if I&amp;rsquo;m explaining something to someone else. Occasionally someone else might actually wander along and learn something too. Bonus!&lt;/p&gt;&#xA;&lt;p&gt;There are a few ways to create chords on the M8.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Using multiple tracks - there&amp;rsquo;s also a trick to automate this somewhat using internal MIDI routing, but it still uses multiple tracks&lt;/li&gt;&#xA;&lt;li&gt;There are a few modules in the MacroSyn instrument that can create chords - the Triple Sine, Saw, Square, etc, Wav Paraphonic, and probably some others&lt;/li&gt;&#xA;&lt;li&gt;The HyperSyn instrument&lt;/li&gt;&#xA;&lt;li&gt;The FM Synth instrument&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Using multiple tracks is pretty straighforward. It does use up tracks, but you can always bounce them down to a sample once you have a sound you like. Of course, if you make any changes you have to go back to the source and re-bounce. Using this method, you can also use samples as voices for the chord, which isn&amp;rsquo;t possible in any other method that I know of.&lt;/p&gt;&#xA;&lt;p&gt;The MacroSyn modules do chords, but I haven&amp;rsquo;t found any in depth documentation for them (even the original Braids documentation is not super helpful). It&amp;rsquo;s probably worth digging into more, or trying to find more on this. Wav Paraphonic seems very powerful.&lt;/p&gt;&#xA;&lt;p&gt;The HyperSyn instrument is amazing. Super easy to create multiple chords and easy to select one for any given note. I&amp;rsquo;ve used it a lot. But after a while, it just starts to sound the same. You have six intervals, each of which is composed of two saw waves that can be detuned. Plus a square sub bass. There&amp;rsquo;s probably a lot more I could dive into, but I haven&amp;rsquo;t really managed to pull out a lot of variety from this one. Saws will be saws.&lt;/p&gt;&#xA;&lt;p&gt;Which brings us to the FM Synth solution. This is a well known route, but I started exploring it recently and found some pretty good tricks. It&amp;rsquo;s especially useful since version 5 of the M8 firmware, which adds wavetables to the voices you can choose in the instrument.&lt;/p&gt;&#xA;&lt;p&gt;A lot of this is pretty obvious and has been used and documented well by others. But I think I&amp;rsquo;ve been able to work out a few tricks that someone might find useful.&lt;/p&gt;&#xA;&lt;h2 id=&#34;setting-up-the-synth&#34;&gt;Setting up the Synth&lt;/h2&gt;&#xA;&lt;p&gt;Basically, we&amp;rsquo;re using the FM Synth instrument as not-an-FM-synth. Just four different independent voices. So choose the last algorithm, &lt;code&gt;A+B+C+D&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Next, set each operator to the same voice. Let&amp;rsquo;s try saw for all of them and see if we can replicate the HyperSyn sound.&lt;/p&gt;&#xA;&lt;p&gt;With four operators, we can create a 7th chord. A major 7th would have hex intervals of &lt;code&gt;00&lt;/code&gt;, &lt;code&gt;04&lt;/code&gt;, &lt;code&gt;07&lt;/code&gt;, &lt;code&gt;0B&lt;/code&gt;. We&amp;rsquo;ll enter those with mods. Leave the &lt;code&gt;RATIO&lt;/code&gt;s and &lt;code&gt;LEV/FB&lt;/code&gt;s all at the default. For operators &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt; and &lt;code&gt;D&lt;/code&gt;, create pitch mods for mod 1, 2 and 3. i.e. &lt;code&gt;1&amp;gt;PIT&lt;/code&gt; etc.&lt;/p&gt;&#xA;&lt;p&gt;Then below that set &lt;code&gt;MOD1&lt;/code&gt;, &lt;code&gt;MOD2&lt;/code&gt; and &lt;code&gt;MOD3&lt;/code&gt; to &lt;code&gt;04&lt;/code&gt;, &lt;code&gt;07&lt;/code&gt;, and &lt;code&gt;0B&lt;/code&gt;. Operator &lt;code&gt;A&lt;/code&gt; will not be modded, so it will get the root tone. Everything should look like this now:&lt;/p&gt;&#xA;&lt;img alt=&#34;m8 screen set up with fm synth for a major chord&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;If you play a note with that instrument now, you should hear a decent major 7th chord.&lt;/p&gt;&#xA;&lt;p&gt;You can set up a HyperSyn instrument that will be almost identical like so:&lt;/p&gt;&#xA;&lt;img alt=&#34;m8 screen with hypersyn set up for a major chord&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;If you play these two one after the other, you&amp;rsquo;ll find they sound pretty close.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;m8 showing phrase with two insruments - fm synth and hypersyn, playing very similar chords&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Not 100% the same, but pretty damn close. They&amp;rsquo;re two different synth engines, so there will be differences.&lt;/p&gt;&#xA;&lt;h3 id=&#34;making-the-chord-dynamic&#34;&gt;Making the chord dynamic&lt;/h3&gt;&#xA;&lt;p&gt;Of course, we don&amp;rsquo;t want to hard code the specific chord we&amp;rsquo;re using. So remove the &lt;code&gt;04&lt;/code&gt;, &lt;code&gt;07&lt;/code&gt;, &lt;code&gt;0B&lt;/code&gt; from the mods on the instrument screen. And set the table tick (&lt;code&gt;TBL. TIC&lt;/code&gt;) to 0 so the instrument screens looks like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;m8 screen showing fm synth&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Then let&amp;rsquo;s go into the table for the instrument and create the mods there. In addition to the major 7th, we can create a minor 7th. This is &lt;code&gt;00&lt;/code&gt;, &lt;code&gt;03&lt;/code&gt;, &lt;code&gt;07&lt;/code&gt;, &lt;code&gt;0A&lt;/code&gt;. Again, we only need do to the last three.&lt;/p&gt;&#xA;&lt;img alt=&#34;m8 screen showing a table defining a major 7th and minor 7th chords&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;We&amp;rsquo;ll use &lt;code&gt;THO&lt;/code&gt; (table hop) to select which chord to play. But we only want it to play one chord per triggered note, which is why we set table tic to 00. This causes the table to only advance on a new note.&lt;/p&gt;&#xA;&lt;p&gt;Now let&amp;rsquo;s set a chord progression, the classic I, V, vi, IV. In C major that will be C, G, A minor, F.&lt;/p&gt;&#xA;&lt;p&gt;On each note we use a &lt;code&gt;THO&lt;/code&gt; to set it to the major or minor chord.&lt;/p&gt;&#xA;&lt;img alt=&#34;m8 screen showing a phrase with C, G, A, F&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;If we slow down the tempo to 50 bpm, we get this:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;m8 showing phrase with two insruments - fm synth and hypersyn, playing very similar chords&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_07.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Cool, but all we&amp;rsquo;ve really done here is partially reproduce the HyperSyn. A lot more is possible.&lt;/p&gt;&#xA;&lt;h2 id=&#34;enter-wavetables&#34;&gt;Enter wavetables&lt;/h2&gt;&#xA;&lt;p&gt;As mentioned earlier, version 5 of the M8 firmware lets you use one of 61 different wavetables for each operator. And each wavetable contains 256 different waves. So&amp;hellip; yeah, there&amp;rsquo;s some variety to be had there. 15,616 different sounds - for starters.&lt;/p&gt;&#xA;&lt;p&gt;First, let&amp;rsquo;s set each one of the operators to use the same wavetable. I&amp;rsquo;m choosing &lt;code&gt;W0F&lt;/code&gt;, but you do you.&lt;/p&gt;&#xA;&lt;p&gt;Now, to choose which wave to use, we use the feedback (&lt;code&gt;FBK&lt;/code&gt;) parameter.&lt;/p&gt;&#xA;&lt;p&gt;So, set &lt;code&gt;FBK&lt;/code&gt; of each operator to &lt;code&gt;FF&lt;/code&gt;. And add a mod do each one: &lt;code&gt;4&amp;gt;FBK&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;m8 screen showing the fm synth&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Play the song again, and as it plays, move &lt;code&gt;MOD4&lt;/code&gt; up and down slowly. You&amp;rsquo;ll hear a wide varity of sounds, from lush to harsh to bells to muted to mellow. And that&amp;rsquo;s just with one of the wavetables.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;m8 playing a chord progression with a bunch of different sounds&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_09.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;By the way, if you&amp;rsquo;re wondering why I set &lt;code&gt;FBK&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt; for each operator, my post from the other week might help: &lt;a href=&#34;https://bit-101.com/blog/posts/2025-04-06/m8-fm-wavetable-modulation/&#34;&gt;https://bit-101.com/blog/posts/2025-04-06/m8-fm-wavetable-modulation/&lt;/a&gt; In short, that value sets the maximum feedback. The &lt;code&gt;MOD4&lt;/code&gt; value chooses a value from 0 to that maximum. So if we want to choose among all the wave forms, we set &lt;code&gt;FBK&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt; (255).&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-few-more-tricks&#34;&gt;A few more tricks&lt;/h2&gt;&#xA;&lt;p&gt;Right now every operator is using the exact same wave form. You can go totally wild and choose an entirely different wavetable for each operator. That might get a little chaotic though - like four different instruments playing a chord. But if you find something that works, go for it.&lt;/p&gt;&#xA;&lt;p&gt;For a little more subtle effect, just change the &lt;code&gt;FF&lt;/code&gt; on each operator to something slightly different. This changes the maximum of the range you are choosing waves from, so that each operator&amp;rsquo;s wave will be a little bit offset instead of all being the same. Even something like &lt;code&gt;F9&lt;/code&gt;, &lt;code&gt;FB&lt;/code&gt;, &lt;code&gt;FD&lt;/code&gt;, &lt;code&gt;FF&lt;/code&gt; can make enough of an offset to give each operator a different wave, making a slightly richer sound overall.&lt;/p&gt;&#xA;&lt;p&gt;Also, try modulating &lt;code&gt;MOD4&lt;/code&gt; with an LFO. I&amp;rsquo;d suggest keeping the LFO depth (&lt;code&gt;AMT&lt;/code&gt;) very low, like under 10, or making the LFO speed very low (i.e. setting &lt;code&gt;FREQ&lt;/code&gt; high). Otherwise you&amp;rsquo;ll be ripping through a ton of different waves too fast. But again, if that&amp;rsquo;s what you want, do it. But a low depth, fast LFO gives a kind of vibrato, a higher depth, low speed, gives an evolving texture change. Great for drones or other long, drawn out chords.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;m8 playing a chord progression and modulating the wave in the wavetable&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-04-22/chords_10.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Naturally, play with filters, amps, limiters, chorus, delay and reverb, and anything else you can think of.&lt;/p&gt;&#xA;&lt;h2 id=&#34;add-more-chords&#34;&gt;Add more chords&lt;/h2&gt;&#xA;&lt;p&gt;In this demo, I only set up a major 7th and minor 7th. But you can add whatever chords you want - augmented, diminished, suspended, inversions, etc. Just trigger them with a &lt;code&gt;THO&lt;/code&gt; to the right row.&lt;/p&gt;&#xA;&lt;p&gt;One thing I haven&amp;rsquo;t figured out a perfect solution for is how to have triads and four-note chords in the same instrument. For a triad, you&amp;rsquo;d want to set operator &lt;code&gt;D&lt;/code&gt;&amp;rsquo;s level to 0. But the only way to do dynamically that is with a mod. And we&amp;rsquo;re plumb out of mods.&lt;/p&gt;&#xA;&lt;p&gt;There are two solutions I&amp;rsquo;ve thought of. One is to just have two instruments. One with only three operators playing - for triads, and another with all four - for 7ths or whatever.&lt;/p&gt;&#xA;&lt;p&gt;Another solution is for your triads, just set operator &lt;code&gt;D&lt;/code&gt;s mod to one of the other notes, either &lt;code&gt;00&lt;/code&gt;, &lt;code&gt;04&lt;/code&gt;, or &lt;code&gt;07&lt;/code&gt; (for a major chord). This does make one note of the chord louder than the other two though. So for now I&amp;rsquo;m just going the separate instrument route. If anyone has any other ideas I haven&amp;rsquo;t thought of, let me know.&lt;/p&gt;&#xA;&lt;p&gt;In parting, here&amp;rsquo;s a track I did while figuring all this out. The main chords that lead off the tune are created exactly as described in this article. I think they sound pretty good.&lt;/p&gt;&#xA;&lt;iframe width=&#34;100%&#34; height=&#34;300&#34; scrolling=&#34;no&#34; frameborder=&#34;no&#34; allow=&#34;autoplay&#34; src=&#34;https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/2082181533&amp;color=%23ff5500&amp;auto_play=false&amp;hide_related=false&amp;show_comments=true&amp;show_user=true&amp;show_reposts=false&amp;show_teaser=true&amp;visual=true&#34;&gt;&lt;/iframe&gt;&lt;div style=&#34;font-size: 10px; color: #cccccc;line-break: anywhere;word-break: normal;overflow: hidden;white-space: nowrap;text-overflow: ellipsis; font-family: Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif;font-weight: 100;&#34;&gt;&lt;a href=&#34;https://soundcloud.com/bit101&#34; title=&#34;bit101&#34; target=&#34;_blank&#34; style=&#34;color: #cccccc; text-decoration: none;&#34;&gt;bit101&lt;/a&gt; · &lt;a href=&#34;https://soundcloud.com/bit101/alternate-85&#34; title=&#34;Alternate 85&#34; target=&#34;_blank&#34; style=&#34;color: #cccccc; text-decoration: none;&#34;&gt;Alternate 85&lt;/a&gt;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>M8 FM Wavetable Modulation</title>
      <link>https://bit-101.com/blog/posts/2025-04-06/m8-fm-wavetable-modulation/</link>
      <pubDate>Sun, 06 Apr 2025 15:44:28 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-04-06/m8-fm-wavetable-modulation/</guid>
      <description>&lt;p&gt;OK, what the hell is that subject?&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m going to talk about a sound design technique I figured out on the &lt;a href=&#34;https://dirtywave.com&#34;&gt;Dirtywave M8&lt;/a&gt; hardware tracker device that can make some cool evolving sounds.&lt;/p&gt;&#xA;&lt;p&gt;Most of what I&amp;rsquo;m talking about here won&amp;rsquo;t make a whole lot of sense if you don&amp;rsquo;t have an M8 device (or the headless version you can set up yourself). But even if you don&amp;rsquo;t, you might find it interesting.&lt;/p&gt;&#xA;&lt;h2 id=&#34;update-4725&#34;&gt;Update 4/7/25&lt;/h2&gt;&#xA;&lt;p&gt;I just realized a simpler way of doing this and updated the post with it. I left the original post as-is because I think it&amp;rsquo;s good for understanding, and might be the best way to do things in some cases. But make sure you read to the end for the simpler way.&lt;/p&gt;&#xA;&lt;h2 id=&#34;fm-synthesis&#34;&gt;FM Synthesis&lt;/h2&gt;&#xA;&lt;p&gt;So the M8 has an FM synthesis module, which is very powerful for a small handheld device like this. But we&amp;rsquo;re not going to talk about FM sysnthesis at all here. The important thing is that the FM synthesizer has four oscillators. In FM synthesis, you&amp;rsquo;d have some of those oscillators modulate other oscillators. But we&amp;rsquo;re just going to use one of them and ignore the rest. Also, classic FM synthesis generally sticks with sine waves, but the M8 has a bunch of different waves you can choose, including a whole crap ton of wavetables. This is kind of insane, but can be very powerful. Each oscillator has a feedback parameter, which usually feeds the signal back into itself. But if you&amp;rsquo;re using a wavetable, the feedback parameter is used to index a particular shape in the chosen wavetable.&lt;/p&gt;&#xA;&lt;h2 id=&#34;setting-up-the-wavetable&#34;&gt;Setting up the Wavetable&lt;/h2&gt;&#xA;&lt;p&gt;OK, so the M8 comes with 61 wavetables built in. They are numbered (in hexadecimal) from &lt;code&gt;09&lt;/code&gt; to &lt;code&gt;45&lt;/code&gt;. The M8 manual even shows all the waves in each table. Here&amp;rsquo;s the first eight:&lt;/p&gt;&#xA;&lt;img alt=&#34;a page of small wave forms&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/wavetables_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;OK, it doesn&amp;rsquo;t show all the waves, but it does show one out of four for each table. This is good enough because the missing ones are just interpolated between the shown ones. As you can see, each wave has a hex value from &lt;code&gt;00&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a close up:&lt;/p&gt;&#xA;&lt;img alt=&#34;a closeup of a page of small wave forms&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/wavetables_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Say we wanted the wave in the center there, number &lt;code&gt;34&lt;/code&gt;. Looking at the top of that column, I see that&amp;rsquo;s in table &lt;code&gt;35&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve set up an FM synth instrument in a phrase on the M8. I&amp;rsquo;ve chosen algorithm &lt;code&gt;0B&lt;/code&gt; which just outputs each oscillator separately - no FM synthesis. And I set levels of the first three oscillators (A to C)  to 0 and cranked up the final one (D) to &lt;code&gt;AF&lt;/code&gt; just to make the wave&amp;rsquo;s amplitude as visible as possible without clipping. I set oscillator D&amp;rsquo;s wave to &lt;code&gt;W35&lt;/code&gt; to get the right table, and set the feedback to &lt;code&gt;34&lt;/code&gt; to get the right wave in that table. It&amp;rsquo;s playing a D#5 note, and as you can see, it looks pretty close to wave &lt;code&gt;34&lt;/code&gt; shown in the manual. Hurrah! First step accomplished.&lt;/p&gt;&#xA;&lt;img alt=&#34;instrument page showing the settings and resulting wave form&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/m8_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Side note: it&amp;rsquo;s unlikely you&amp;rsquo;ll often want to do sound design by choosing interesting wave shapes. Though there are examples of just that out there. But I&amp;rsquo;m just walking through this in order to understand deeply how this all works. Bear with me.&lt;/p&gt;&#xA;&lt;h2 id=&#34;modulating-a-wave&#34;&gt;Modulating a Wave&lt;/h2&gt;&#xA;&lt;p&gt;OK. We have a single wave playing. Now we want to modulate it. i.e. make it move between different shapes, which will change the way the tone sounds. Well, we can just modulate the feedback parameter.&lt;/p&gt;&#xA;&lt;p&gt;Here, I&amp;rsquo;ve added a modulator for oscillator D, &lt;code&gt;1 &amp;gt; FBK&lt;/code&gt;. This means modulator 1 will affect feedback.&lt;/p&gt;&#xA;&lt;img alt=&#34;instrument page showing the settings and resulting wave form&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/m8_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;But you&amp;rsquo;ll notice that the waveform is no longer what we originally had. In fact, it matches wave &lt;code&gt;00&lt;/code&gt; of table &lt;code&gt;35&lt;/code&gt;. And if you could hear the tone playing, you&amp;rsquo;d notice it has changed.&lt;/p&gt;&#xA;&lt;img alt=&#34;a closeup of the first couple of waves in a table&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/wavetables_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;So what&amp;rsquo;s happening here? Well, in order to modulate something we need two values to modulate between - the start point and the end point. The value we enter in the oscillator&amp;rsquo;s settings is the end point. The start point is what&amp;rsquo;s shown in the &lt;code&gt;MOD1&lt;/code&gt; field just below and to the far left. It&amp;rsquo;s &lt;code&gt;00&lt;/code&gt;. If you move &lt;code&gt;MOD1&lt;/code&gt; up to &lt;code&gt;FF&lt;/code&gt;, that will make the minimum and maximum values of the modulation the same, and we&amp;rsquo;ll be back to where we started, including having the right sound back.&lt;/p&gt;&#xA;&lt;img alt=&#34;instrument page showing the settings and resulting wave form&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/m8_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;So we need to set &lt;code&gt;MOD1&lt;/code&gt; to the minimum, or start value of the modulation. This is where it gets a bit complicated. And a video will help a lot.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-04-06/video_01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I&amp;rsquo;m starting with the same set up as the last screenshot. Then I move &lt;code&gt;MOD1&lt;/code&gt; down to &lt;code&gt;00&lt;/code&gt;. Now we&amp;rsquo;re back to showing wave &lt;code&gt;00&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Then I go into the Mods screen and set up an LFO to control &lt;code&gt;MOD 1&lt;/code&gt;. It&amp;rsquo;s going from the default &lt;code&gt;00&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt; with a triangle wave, and I slowed it down a bit.&lt;/p&gt;&#xA;&lt;p&gt;You can see that the wave is cycling through a whole bunch of different shapes. In fact, it&amp;rsquo;s going through every shape in table &lt;code&gt;35&lt;/code&gt; from &lt;code&gt;00&lt;/code&gt; to &lt;code&gt;34&lt;/code&gt;. And playing a bunch of different sounds as it goes through those different shapes.&lt;/p&gt;&#xA;&lt;p&gt;But say I don&amp;rsquo;t want to go through ALL of those shapes. Maybe just from wave &lt;code&gt;24&lt;/code&gt; to &lt;code&gt;34&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;a closeup of four waves in a table&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/wavetables_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;How to get there?&lt;/p&gt;&#xA;&lt;p&gt;Well, we know that the feedback parameter on the oscillator determines the maximum or end value, and the &lt;code&gt;MOD1&lt;/code&gt; setting below sets the start point, but the start point isn&amp;rsquo;t simply the wave index in this case. It&amp;rsquo;s mapped so that &lt;code&gt;00&lt;/code&gt; is the first wave in the table, and &lt;code&gt;FF&lt;/code&gt; is the maximum set on the oscillator. So the value is kind of a percentage from 0 to the maximum, displayed in hexadecimal. You could do the math, and figure it all out that way, but that would be painful. Instead, just do it by eye.&lt;/p&gt;&#xA;&lt;p&gt;First I&amp;rsquo;m going to remove the LFO by setting its destination to &lt;code&gt;OFF&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;mods page showing LFO turned off.&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/m8_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Then I&amp;rsquo;m going to back to the instrument page, move &lt;code&gt;MOD1&lt;/code&gt; back up to &lt;code&gt;FF&lt;/code&gt; and then slowly decrease it until it looks like wave &lt;code&gt;24&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;mods page showing LFO turned off.&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/m8_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;When I get to &lt;code&gt;D4&lt;/code&gt;, it looks about the same as wave &lt;code&gt;24&lt;/code&gt; I think. Again, in real world sound design, you&amp;rsquo;re probably going to be doing this way more by ear than by eye, so don&amp;rsquo;t feel bad about winging it.&lt;/p&gt;&#xA;&lt;p&gt;Now, I&amp;rsquo;ll turn back on the LFO and see what we get.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-04-06/video_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;OK, it&amp;rsquo;s modulating, but it does from &lt;code&gt;24&lt;/code&gt; to &lt;code&gt;34&lt;/code&gt; in a split second and then just seems to stay at &lt;code&gt;34&lt;/code&gt; for the rest of the cycle.&lt;/p&gt;&#xA;&lt;p&gt;By the way, don&amp;rsquo;t worry about the jumping every few seconds, that&amp;rsquo;s just the note being retriggered as the phrase loops.&lt;/p&gt;&#xA;&lt;p&gt;What&amp;rsquo;s happening now is that the LFO is going from &lt;code&gt;00&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt; and adding that value to the minimum value from the &lt;code&gt;MOD1&lt;/code&gt; setting. That&amp;rsquo;s way too much. So it goes past the maximum value and just gets stuck there.&lt;/p&gt;&#xA;&lt;p&gt;What we want is for the LFO to make &lt;code&gt;MOD1&lt;/code&gt; go from &lt;code&gt;D4&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt;. If we subtract those two, we get &lt;code&gt;2B&lt;/code&gt; in hex. Of course, subtracting hexadecimal numbers is not easy for most humans, so I find it easier to count my way up.&lt;/p&gt;&#xA;&lt;p&gt;I go into the LFO and set the amount to &lt;code&gt;00&lt;/code&gt; then count up from &lt;code&gt;D4&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt;. First by the left digit, hitting the up arrow: &lt;code&gt;E4&lt;/code&gt;, &lt;code&gt;F4&lt;/code&gt;, which gives you &lt;code&gt;20&lt;/code&gt;. Then by the right digit, hitting the right arrow for &lt;code&gt;F5&lt;/code&gt;, &lt;code&gt;F6&lt;/code&gt;, &lt;code&gt;F7&lt;/code&gt;, &lt;code&gt;F8&lt;/code&gt;, &lt;code&gt;F9&lt;/code&gt;, &lt;code&gt;FA&lt;/code&gt;, up to &lt;code&gt;FF&lt;/code&gt;. When you&amp;rsquo;re done, you should have &lt;code&gt;2B&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;When you play this now, it&amp;rsquo;s just going to cycle in the exact range we want.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-04-06/video_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I also made the chain a bit longer so there aren&amp;rsquo;t as many jumps.&lt;/p&gt;&#xA;&lt;p&gt;In case that&amp;rsquo;s not completely clear, here&amp;rsquo;s a diagram that might help.&lt;/p&gt;&#xA;&lt;img alt=&#34;diagram showing the range of waves mapped to a minimum and maximum&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/diagram_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The bottom arrow represents the full range of waves in the table, &lt;code&gt;00&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;You can see the minimum and maximum we want: &lt;code&gt;24&lt;/code&gt; and &lt;code&gt;34&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;We set &lt;code&gt;MOD1&lt;/code&gt; so it corresponds with the minimum, and make up the rest in the LFO.&lt;/p&gt;&#xA;&lt;h2 id=&#34;final-project&#34;&gt;Final Project&lt;/h2&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2025-04-06/video_04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;For a (somewhat) more real-world example, and to break down the steps&amp;hellip;&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;I chose a different table - &lt;code&gt;3B&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;I turned off the LFO.&lt;/li&gt;&#xA;&lt;li&gt;Set &lt;code&gt;MOD1&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Found an end point I liked the sound of - &lt;code&gt;3C&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Moved &lt;code&gt;MOD1&lt;/code&gt; down till I found a starting point I liked - &lt;code&gt;6F&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Then set the LFO to the difference of &lt;code&gt;FF&lt;/code&gt; and &lt;code&gt;6F&lt;/code&gt;, which is &lt;code&gt;90&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I did all this by ear, not looking at tables and wave numbers.&lt;/p&gt;&#xA;&lt;p&gt;This gives a complex, rhythmic sound, even though only a single note is playing.&lt;/p&gt;&#xA;&lt;p&gt;Of course, instead of a free running triangle LFO, you could use a ramp or some other shape, retriggered, or just use an envelope of your choice.&lt;/p&gt;&#xA;&lt;p&gt;The main point of this post was just figuring out the start and end points of the modulation and how to set them, which was not at all obvious to me for quite a while.&lt;/p&gt;&#xA;&lt;p&gt;Also, I&amp;rsquo;m still a bit of a noob with this device. In fact, I&amp;rsquo;m using the headless version still, though my real M8 should be arriving this week I hope! Anyway, if there&amp;rsquo;s anything incorrect in this post, please let me know.&lt;/p&gt;&#xA;&lt;h2 id=&#34;update&#34;&gt;Update&lt;/h2&gt;&#xA;&lt;p&gt;I figured out a simpler way of doing this.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Set the feedback param of the oscillator to &lt;code&gt;FF&lt;/code&gt;. This makes the modulation range &lt;code&gt;00&lt;/code&gt; to &lt;code&gt;FF&lt;/code&gt;, which exactly map to the wave indexes, so all values used will map exactly to the wave numbers.&lt;/li&gt;&#xA;&lt;li&gt;Set &lt;code&gt;MOD1&lt;/code&gt; (or whichever mod you are using) to the minimum value, say &lt;code&gt;24&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Now set up the LFO with the range you want your modulation to cover. If you want the modulation to go from &lt;code&gt;24&lt;/code&gt; to &lt;code&gt;34&lt;/code&gt;, set the amount to &lt;code&gt;10&lt;/code&gt;. It will add &lt;code&gt;10&lt;/code&gt; to the minimum &lt;code&gt;24&lt;/code&gt;, making it range up to &lt;code&gt;34&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;mods page showing LFO turned off.&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/m8_06.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;mods page showing LFO turned off.&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2025-04-06/m8_08.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;I think both ways are still valid and result in the same sound, so which one you use is up to you.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Plex, Navidrome, and Symfonium</title>
      <link>https://bit-101.com/blog/posts/2025-03-29/plex-navidrome-symfonium/</link>
      <pubDate>Sat, 29 Mar 2025 17:37:56 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-03-29/plex-navidrome-symfonium/</guid>
      <description>&lt;h2 id=&#34;music&#34;&gt;Music&lt;/h2&gt;&#xA;&lt;p&gt;Like most people, I enjoy listening to music. I&amp;rsquo;m also a bit older, so most of the music I listen to is not contemporary. I gravitate towards the music I grew up with, and even more these days I have a lot of affection for the music that I heard when I was really young, the stuff that my mother listened to, or was just on the radio or tv, before I was consciously seeking out popular music. I was born in &amp;lsquo;64, so a lot of that is from the 60s or early 70s. It&amp;rsquo;s super nostalgic to me. And of course, &lt;em&gt;my&lt;/em&gt; decade was the 80s. And I love a lot of the music from the 90s. I do listen to more modern stuff - mostly electronic music of one kind or another, just not so much of what&amp;rsquo;s popular with the kids these days.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s part of the reason I&amp;rsquo;m not a huge fan of streaming services. I don&amp;rsquo;t pay for Spotify, but I do have a paid account on Tidal. While both of those services have plenty of good older music, their main business model is pushing out the new and contemporary tunes. So I do use Tidal somewhat, but I&amp;rsquo;m mostly interested in curating and collecting my own library of music. I&amp;rsquo;ve been doing that for years and have a nice collection (almost 14,000 songs), ripped into lossless FLAC for the most part, organized into folders, all correctly tagged, album art added, even with all the replay gain settings calculated and added to the metadata of every single song. Yeah, I&amp;rsquo;m a bit obsessive about it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-does-all-that-music-get-into-my-ears&#34;&gt;How does all that music get into my ears?&lt;/h2&gt;&#xA;&lt;p&gt;If I never left the house, I could just have my libary on some device connected to a stereo system. But I do go into the office now and then, travel, walk the dog, drive the car around, go for runs, etc. All that music is too much to carry around on a phone - at least now that most phones don&amp;rsquo;t take extra storage. Enter&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;personal-streaming&#34;&gt;Personal streaming&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m talking about straming my own curated library from a self-hosted server. For a while I was using Google Music and Amazon Music. Both had services where you could upload your own music and stream it. Neither one does now. It was decent while it lasted, but when it was gone, I needed to find something else.&lt;/p&gt;&#xA;&lt;h3 id=&#34;subsonic&#34;&gt;Subsonic&lt;/h3&gt;&#xA;&lt;p&gt;When you start looking into music servers, you&amp;rsquo;ll find a wide selection. But most of them are forks of a fairly old server called &lt;a href=&#34;https://www.subsonic.org&#34;&gt;Subsonic&lt;/a&gt;. It started out as an open source project but eventually went with a closed license and offered a paid, premium version. In 2017, a fork of the open source version was launched, &lt;a href=&#34;https://airsonic.github.io/&#34;&gt;AirSonic&lt;/a&gt;. Over the years other forks emerged, FutureSonic, MadSonic, AirSonic Advanced, LibreSonic, Gonic, Ampache, Navidrome, and others. There&amp;rsquo;s even BookSonic that&amp;rsquo;s tailored to audiobooks. I don&amp;rsquo;t know of all of these are forks specifically, but they use the same Subsonic API, so they&amp;rsquo;re compatible.&lt;/p&gt;&#xA;&lt;p&gt;The fact that all of these servers use the same API means that any Subsonic compatible client will work with them. And there are a TON of clients. Mac, Windows, and Linux desktop clients, Android and iOS clients, multiple web clients, even terminal clients. There are even hardware devices that will connect to these servers.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve used a number of these in the past, and most of them work fairly well if you&amp;rsquo;re playing a particular artist or album or playlist. The big frustration for me was in trying to just get a random (shuffled) stream going. Either going to all tracks and choosing shuffle, or even choosing a particular genre and shuffling that. Instead of shuffling all the tracks in the chosen library, every client/server combo I tried would do something like grab the first couple of &lt;em&gt;albums&lt;/em&gt; in the list and shuffle the two dozen or so songs in them. Not at all what I wanted. I tried everything and never got past this point, and finally gave up.&lt;/p&gt;&#xA;&lt;h2 id=&#34;screw-streaming&#34;&gt;Screw streaming&lt;/h2&gt;&#xA;&lt;p&gt;At this point I decided to forget about streaming and got a dedicated digital audio player (DAP). I wound up with several of these over the next few years. They accept large capacity micro SD cards so they&amp;rsquo;ll easily hold even a large library, and they&amp;rsquo;re built with one purpose in mind - playing music. They have high quality circuitry for playing back high resolution audio files and usually have plenty of high end features for tuning the sound to your liking. This has been great, and serves most of my purposes, but not all.&lt;/p&gt;&#xA;&lt;img alt=&#34;hiby r4 player&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-03-29/hiby.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;changing-gears-plex&#34;&gt;Changing gears: Plex&lt;/h2&gt;&#xA;&lt;p&gt;In addition to music, I have a decent library of movies and tv shows. I&amp;rsquo;ve used various servers for these over the years as well. One or two media servers that I think grew out of the old Xbox Media Server, Kodi, Jellyfin, and Plex. All of these worked pretty well. Around the time where I mostly moved over to DAPs for music, I was using Plex. I still wanted a streaming service for occasional use cases. Running for one. I usually carry my phone on a run, and didn&amp;rsquo;t want to carry a second device for music. Or sometimes I was just out somehwere and hadn&amp;rsquo;t brought a DAP and wanted to listen to music on my phone. I realized that Plex, in addition to serving movies, also serves music. It has a dedicated app for that - PlexAmp. I tried it out and was blown away. It was the best mobile streaming music app I&amp;rsquo;d used. It handled random playlists of everything or just a genre perfectly. It even goes beyond that, with smart playlists based on a mood or music type or using songs or artists as a starting point.&lt;/p&gt;&#xA;&lt;img alt=&#34;plexamp&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-03-29/plexamp.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;In order to use PlexAmp, you need to have a PlexPass, which at the time was like $39.99 for a year. Or $119 for a lifetime pass. I felt it was worth it and paid for probably three years. The pass also gives you other features of Plex for videos. Don&amp;rsquo;t ask me what they are though. I really only bought it for PlexAmp. In retrospect I should have gone with the lifetime pass. And probably would have this year. But&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;screw-plex&#34;&gt;Screw Plex&lt;/h2&gt;&#xA;&lt;p&gt;So all was good for a while. Then I got an email the other day that Plex was raising its prices and making other changes.&lt;/p&gt;&#xA;&lt;p&gt;The annual PlexPass was going from $39.99 to $69.99. Damn. That&amp;rsquo;s nearly double. And lifetime was going from $119 to $250. Holy cow, that&amp;rsquo;s more than double. In addition they were making it so you need a PlexPass to stream anything outside your local network. The change goes into effect soon, so I had to decide if I wanted to go with lifetime for the current lower price, or move on to something else. I decided to test the Subsonic clones anew. It&amp;rsquo;s been a good few years since my last attempt.&lt;/p&gt;&#xA;&lt;h2 id=&#34;navidrome-and-symfonium&#34;&gt;Navidrome and Symfonium&lt;/h2&gt;&#xA;&lt;p&gt;I did some research on the current clones and &lt;a href=&#34;https://www.navidrome.org/&#34;&gt;Navidrome&lt;/a&gt; was getting high points. Many of the clones were out of active development and were never much more than straight up clones. Navidrome seemed to put a lot more into their offering and were still active. I installed it on my home server via Docker. It was a piece of cake.&lt;/p&gt;&#xA;&lt;p&gt;As for an Android client, I went with &lt;a href=&#34;https://www.symfonium.app/&#34;&gt;Symfonium&lt;/a&gt;. I recalled that it was hands down the best Subsonic compatible Android app before, and it was still getting high ratings. Got it installed and hooked up to my Navidrome server. Worth noting that I&amp;rsquo;ve seen the Navidrome/Symfonium coupling mentioned a few times as the ideal team.&lt;/p&gt;&#xA;&lt;p&gt;The verdict?&lt;/p&gt;&#xA;&lt;p&gt;Great! This honestly is a very good client/server pair. I&amp;rsquo;m really impressed with the current version of Symfonium. It comes close to the features of PlexAmp in a lot of ways. It&amp;rsquo;s got built in stuff for track or album or even decade mixes. You can also create a smart playlist where you can specify a whole load of parameters and it will choose any number of songs matching those.&lt;/p&gt;&#xA;&lt;img alt=&#34;Symfonium player&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-03-29/Symfonium.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Again, this is my secondary use case, for when I don&amp;rsquo;t have my DAP on me, or whatever. But that happens fairly often. Honestly it might just be that my DAP is upstairs and I want to listen to some music on my phone or laptop without having to go upstairs to get the DAP.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-about-movies&#34;&gt;What about movies?&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m going to cancel my PlexPass when this current year is done. At that point, I&amp;rsquo;ll only be able to watch my movies on my local network. But I&amp;rsquo;m not sure I&amp;rsquo;ve done anything else to be honest. So that will be fine. I might try out Jellyfin.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Q1 Update</title>
      <link>https://bit-101.com/blog/posts/2025-03-09/q1-update/</link>
      <pubDate>Sun, 09 Mar 2025 18:21:39 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-03-09/q1-update/</guid>
      <description>&lt;p&gt;Just a general update on what&amp;rsquo;s been happening since my last ramblings.&lt;/p&gt;&#xA;&lt;h2 id=&#34;m8&#34;&gt;M8&lt;/h2&gt;&#xA;&lt;p&gt;Mainly just making music. Still rocking on the &lt;a href=&#34;https://dirtywave.com&#34;&gt;DirtyWave M8&lt;/a&gt;. Actually, still on the headless version, which runs on a Teensy 4.1 board plugged into my computer. I did manage to get in on the last M8 order window on February 10! So I have an actual hardware unit coming at some point - though they won&amp;rsquo;t be shipping until early April. Torture.&lt;/p&gt;&#xA;&lt;p&gt;So many other M8 users express similar love for the device. Many say that they have stopped buying a lot of other musical equipment because this thing just scratches so many itches. I see the same thing in myself. I keep looking at other synths and trackers and devices, but I really just want to get my M8.&lt;/p&gt;&#xA;&lt;p&gt;I hate to sound dramatic, but this device has changed my life. It&amp;rsquo;s just so much fun to make music on. Two years ago I said I wanted to make and release a song. I kind of did that in SuperCollider, but most of that was just sound design and generating random (or semi-random) notes. Not that there&amp;rsquo;s anything wrong with that, but I dreamed of creating a song that was properly put together - all the right pieces handcrafted and thoughfully put in place and all working together. So far this year, &lt;a href=&#34;https://keithpeters.org&#34;&gt;I&amp;rsquo;ve put out twelve songs&lt;/a&gt;. I&amp;rsquo;ve been aiming for one a week. Week 10 of 2025 just wrapped up, so I&amp;rsquo;m a bit ahead of the game. Not saying my songs are amazing, but I think some of them are pretty catchy. More to the point, I&amp;rsquo;m enjoying the hell out of the process.&lt;/p&gt;&#xA;&lt;p&gt;Doing graphics code for 25 years was great, but when I look back at the early days, I was learning something new every damn day and it was so exciting. For the past many years, I&amp;rsquo;ve been doing a lot of revisiting of old projects, doing them better, combining one technique with another technique to make something new, occasionally learning something new, but not often enough.&lt;/p&gt;&#xA;&lt;p&gt;But starting in on music, I&amp;rsquo;m back in that space of constantly discovering something new almost every day. I love it.&lt;/p&gt;&#xA;&lt;p&gt;For my last song, I dug up a short animation I made last year and extended and expanded it and made a video of that along with the song.&lt;/p&gt;&#xA;&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/0yq2P2qa7B4?si=O1kNqR64cX_EezGO&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;The video is not reactive to the sound, or vice-versa, but I still like how it came out. Also this song has been stuck in my head for a week. I&amp;rsquo;ve never created my own earworm before.&lt;/p&gt;&#xA;&lt;h2 id=&#34;gravitar&#34;&gt;Gravitar&lt;/h2&gt;&#xA;&lt;p&gt;While I wait for my actual M8, I tried another headless build, using a handheld retro gaming console. You can plug the Teensy into that and run the headless app on the console. I got it working, but I find I actually like working on the computer a bit more.&lt;/p&gt;&#xA;&lt;p&gt;The device I got was the Anbernic RG40XX V. No big loss on not using it for music though. I&amp;rsquo;m having a blast gaming on it. My current favorite is an old arcade game, Gravitar.&lt;/p&gt;&#xA;&lt;img alt=&#34;gravitar on a game console&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-03-09/gravitar.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;But also playing other old arcade games and 3D FPSes like the original Quake, Doom and Wolfenstein.&lt;/p&gt;&#xA;&lt;img alt=&#34;quake on a game console&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-03-09/quake.jpg&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;work&#34;&gt;Work&lt;/h2&gt;&#xA;&lt;p&gt;Work&amp;rsquo;s been OK. We had a company offsite in Ft. Lauderdale, FL in February. It was a cold spell up here in the Boston area, so that was a welcome relief for a few days. Even got to go on a group run with a bunch of coworkers. That was a highlight.&lt;/p&gt;&#xA;&lt;img alt=&#34;group of runners on a bridge&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-03-09/run.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;(We were staying at the Hilton you can see in the background.)&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m working on hiring a senior front end software engineer. The posting got over 1600 applicants in the first few days. That&amp;rsquo;s pretty scary. Many of them have 10 or more years experience. It&amp;rsquo;s tough out there. Makes me feel bad that I can only hire one. Expecting this role to be filled this coming week. I just hope I am not in the position of looking for a position myself any time soon.&lt;/p&gt;&#xA;&lt;h2 id=&#34;and&#34;&gt;And&amp;hellip;&lt;/h2&gt;&#xA;&lt;p&gt;I guess there is no and. That&amp;rsquo;s pretty much me over the past month or so. And extrapolate all that out into the next month, and you&amp;rsquo;re probably on track with what I&amp;rsquo;ll be doing then. But I&amp;rsquo;ll let you know.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Archive</title>
      <link>https://bit-101.com/blog/pages/archive/</link>
      <pubDate>Sun, 09 Mar 2025 17:48:47 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/pages/archive/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://bit-101.com&#34;&gt;bit-101.com&lt;/a&gt; as a domain went live on September 11, 2001. A couple years later I started a blog. Initially I used Moveable Type (I had to do some digging to remember that) and eventually moved on to WordPress. You can find that here:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;href=%22https://bit-101.com/2003&#34;&gt;Version 1: 2003 - 2017&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;1,236 posts&lt;/li&gt;&#xA;&lt;li&gt;9,861 comments&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;After 14 years, the first version became pretty bloated and a maintenance nightmare. I rebooted it in 2017 with a new WordPress install.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/2017&#34;&gt;Version 2: 2017 - 2023&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;147 posts&lt;/li&gt;&#xA;&lt;li&gt;248 comments&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Six years later, I was pretty disillusioned with WP and fired up Hugo to make a new static site. As of 2025, I&amp;rsquo;m still pretty happy with this setup.&lt;/p&gt;&#xA;&lt;p&gt;The 2017-2023 version was converted from WordPress to Hugo in 2024. This made sense since it had become read-only anyway. Eventually I&amp;rsquo;ll get around to doing that for the 2003-2017 site.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Been Making Noise</title>
      <link>https://bit-101.com/blog/posts/2025-01-25/been-making-noise/</link>
      <pubDate>Sat, 25 Jan 2025 13:49:11 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2025-01-25/been-making-noise/</guid>
      <description>&lt;p&gt;In my &lt;a href=&#34;https://bit-101.com/blog/posts/2024-12-29/2024-retrospective/&#34;&gt;2024 retrospective&lt;/a&gt; I mentioned wanting to create some finished songs and put them out there in the world. And I suspected that the &lt;a href=&#34;https://www.jonathanmann.net/jamuary&#34;&gt;#jamuary&lt;/a&gt; project might be a way to do so without worrying about the songs being too polished. &amp;ldquo;Hey, it&amp;rsquo;s just a jam!&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;Well this has worked out well - way beyond what I imagined. To date I&amp;rsquo;ve put out I think seven (SEVEN) songs! And they are all masterpieces. NOT. But some of them are OK in my opinion. Most of all, I&amp;rsquo;m just having a ton of fun doing this. I&amp;rsquo;m probably a bit obsessed to be honest.&lt;/p&gt;&#xA;&lt;p&gt;Something just clicked this month. It&amp;rsquo;s been a couple of years that I&amp;rsquo;ve been dipping my toes in and out of trying to learn a bit of music here and there. SuperCollider, LMMS, Ardour, Ableton Live, various hardware synths and MIDI controllers, Reaper, other apps, various books, courses, videos, etc.&lt;/p&gt;&#xA;&lt;p&gt;And then&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;renoise&#34;&gt;Renoise&lt;/h2&gt;&#xA;&lt;p&gt;The day before New Years Eve, I woke up not feeling good. By noon I had a fever, by dinner time I had tested positive for Covid. My first bout with it, so I can&amp;rsquo;t complain, but It knocked me down and kept me down for most of a week. During some moments of lucidity though I was watching some Youtube videos and stumbled upon this:&lt;/p&gt;&#xA;&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/roBkg-iPrbw?si=A_PD4DeOxVtK1MRV&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;I had played with some trackers in the 80s on my Amiga and dabbled with them a few times since. But I saw &lt;a href=&#34;https://www.renoise.com/&#34;&gt;Renoise&lt;/a&gt; mentioned in there and it was something I&amp;rsquo;d heard of more recently. It&amp;rsquo;s a modern tracker. In fact, it&amp;rsquo;s fair to say that it&amp;rsquo;s a full fledged DAW (digital audio workstation) that happens to have a tracker interface. You can use high res samples and just about any kind of synth or effect plugins via VST and other standards. MIDI in and out. Which is to say, it&amp;rsquo;s not just chiptune stuff.&lt;/p&gt;&#xA;&lt;img alt=&#34;renoise ui&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-01-25/renoise.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;On the first day messing with it, I managed to put together a full jam that I actually really liked and felt good enough to release.&lt;/p&gt;&#xA;&lt;iframe loading=&#34;lazy&#34; src=&#34;https://keithpeters.org/happy-sea-creature/embed/1/&#34; style=&#34;min-width: 480px;&#34; title=&#34;Audio player widget for &amp;quot;Happy Sea Creature&amp;quot;&#34;&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;Over the next week or so, I banged out another couple of songs. The tracker technique just really seemed to work for me.&lt;/p&gt;&#xA;&lt;h2 id=&#34;dirtywave-m8-tracker&#34;&gt;Dirtywave M8 tracker&lt;/h2&gt;&#xA;&lt;p&gt;Having realized that the tracker paradigm gave me super powers, my attention went to a device that I&amp;rsquo;d been eyeing off and on for months. The &lt;a href=&#34;https://dirtywave.com/&#34;&gt;Dirtywave M8&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;dirtywave m8 tracker&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-01-25/m8.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;This is a small, gorgeous looking device, machined aluminum case, display panel, and all of eight keys. Basically a handheld tracker.&lt;/p&gt;&#xA;&lt;p&gt;There are other hardware trackers such as the &lt;a href=&#34;https://polyend.com/tracker-plus/&#34;&gt;Polyend Tracker&lt;/a&gt; and &lt;a href=&#34;https://polyend.com/tracker-mini/&#34;&gt;Polyend Tracker Mini&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;polyend tracker and tracker mini&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-01-25/polyend.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;The full size Polyend Tracker is definitely a desktop device. The Mini is also large in comparison to the M8:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;6.7 x 5.12 inches (170 x 130mm) for the Mini&lt;/li&gt;&#xA;&lt;li&gt;5.2 x 3.77 inches (113 x 96mm) for the M8&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a screenshot &lt;a href=&#34;https://www.youtube.com/watch?v=CqY8PtYBvoE&#34;&gt;from a video&lt;/a&gt; which shows the sizes more clearly. (This shows the first model of the M8, which is a bit different than the model 2, but the same size.)&lt;/p&gt;&#xA;&lt;img alt=&#34;m8 and tracker mini&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-01-25/m8vsmini.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The M8 is just so much more compact and portable. Something you can jam with on the train or at a restaurant or a park and most people would just think you were on your phone or playing some kind of game. The Mini is more like a very thick iPad. It doesn&amp;rsquo;t hurt that the M8 is a bit cheaper too.&lt;/p&gt;&#xA;&lt;p&gt;The M8 has its history in software that was created for the GameBoy, called &lt;a href=&#34;https://www.littlesounddj.com/lsd/index.php&#34;&gt;LSDJ&lt;/a&gt; (short for Little Sound DJ).&lt;/p&gt;&#xA;&lt;img alt=&#34;lsdj running on a gameboy&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-01-25/lsdj.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;One of the brilliant things about LSDJ and the M8 is that little menu you see in the bottom left. Each one of the letters represents a particular screen in the overall system. So you can use the left and right arrow controls to go from Song, through Chain, Phrase, Instrument, and Table. Each of those main screens usually offer an up and down option for other screens as well. You can see on the Song screen there that you can go up to Program, or down to Groove.&lt;/p&gt;&#xA;&lt;p&gt;But the other wonderful thing about the M8 is that there is a freely available version of the software that you can download and run on a &lt;a href=&#34;https://www.pjrc.com/store/teensy41.html&#34;&gt;Teensy 4.1&lt;/a&gt; microcontroller, which you can pick up for around $30. This is a headless version of the software, so you need to run something else on a computer or other device to get a display, sound, keyboard and midi input/output.&lt;/p&gt;&#xA;&lt;p&gt;One might think that offering the software for free might cut into sales of the hardware device, but after just a day of playing with the headless version, I was committed to buying a real M8.&lt;/p&gt;&#xA;&lt;p&gt;I won&amp;rsquo;t go into the details of how the M8 works or how to set up the headless version, but &lt;a href=&#34;https://github.com/v3rm0n/awesome-m8&#34;&gt;awesome-m8&lt;/a&gt; should answer all of your questions.&lt;/p&gt;&#xA;&lt;p&gt;I got a 3d printed case made for my Teensy board (thanks &lt;a href=&#34;https://mstdn.social/@lukepeters&#34;&gt;Luke&lt;/a&gt;!), and its pretty much permanently been dangling off my computer for the past week. And I&amp;rsquo;ve already put out a few jams/songs that were done on it. This is the most fun I&amp;rsquo;ve had making music ever. I can&amp;rsquo;t wait to get the real thing.&lt;/p&gt;&#xA;&lt;img alt=&#34;3d printed case for a microcontroller on top of a thinkpad keyboard&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2025-01-25/m8headless.jpg&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;faircamp&#34;&gt;Faircamp&lt;/h2&gt;&#xA;&lt;p&gt;One final update on the subject of making noise.&lt;/p&gt;&#xA;&lt;p&gt;Now that I&amp;rsquo;m making music(al sounds) that I&amp;rsquo;m not afraid to share with others, I&amp;rsquo;ve been thinking about how best to share them. Last year I set up a &lt;a href=&#34;https://soundcloud.com/bit101&#34;&gt;SoundCloud&lt;/a&gt; account and I&amp;rsquo;ve been putting stuff up there. But the free plan is limited in space. I&amp;rsquo;m not close to quota and a paid account is not that bad if I get there, so I might eventually do that. There&amp;rsquo;s Bandcamp, but that&amp;rsquo;s mostly about selling music, and I&amp;rsquo;m not even thinking that way yet.&lt;/p&gt;&#xA;&lt;p&gt;A few months ago I heard of &lt;a href=&#34;https://simonrepp.com/faircamp/&#34;&gt;Faircamp&lt;/a&gt;. This is obviously a response to Bandcamp and some changes that happened there last year. But it&amp;rsquo;s software that creates a static site for your music. I could do this with Hugo (like this site), but it would be a bit of an investment. So I decided to try Faircamp.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s pretty decent! You run the executable and point it at a folder that contains your music, images and some metadata files and it spits out a site. It&amp;rsquo;s really that simple.&lt;/p&gt;&#xA;&lt;p&gt;You can run it in preview mode which sets up a local server to view your changes before you publish them. And there&amp;rsquo;s even a command that will sync your built site to your live server via rsync. It even does all the encoding to whatever formats you tell it to, sets up playlists, rss, embed codes, the whole lot. It&amp;rsquo;s also got some options for selling your music, thought it&amp;rsquo;s not a full fledged point-of-sale system. I haven&amp;rsquo;t actually looked into that side of things because, again, I&amp;rsquo;m not anywhere near there and may never be.&lt;/p&gt;&#xA;&lt;p&gt;One thing is that the customization options are limited. You can set up a site theme and individual release themes with a few color options, but not much more beyond that. But it looks pretty good, so for the utter simplicity it offers, I can&amp;rsquo;t really complain. I would love it if you could include some custom CSS in a future release. Of course, there&amp;rsquo;s probably a way to do that now, but it would take a bit of hacking.&lt;/p&gt;&#xA;&lt;p&gt;I had a domain I wasn&amp;rsquo;t using, &lt;a href=&#34;https://keithpeters.org&#34;&gt;keithpeters.org&lt;/a&gt;. So&amp;hellip; there&amp;rsquo;s my Faircamp site. &lt;em&gt;KP-BARA&lt;/em&gt; is the name I chose for my musical identity, just to keep it a bit differentiated from BIT-101.&lt;/p&gt;&#xA;&lt;p&gt;I haven&amp;rsquo;t put up all the stuff I&amp;rsquo;ve made there. Some I only posted on Mastodon because they were just fun silly things. I guess the ones I put on the site are somewhat worth keeping around. If all goes well, I&amp;rsquo;ll look back in a year and be embarrassed by them because what I&amp;rsquo;m making then will be so much better!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>2024 Retrospective</title>
      <link>https://bit-101.com/blog/posts/2024-12-29/2024-retrospective/</link>
      <pubDate>Thu, 02 Jan 2025 17:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-12-29/2024-retrospective/</guid>
      <description>&lt;p&gt;What happened in 2024. A rough guide to things that I remember.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;January&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I participated in &lt;a href=&#34;https://genuary.art/&#34;&gt;Genuary&lt;/a&gt;. Every day. Made some really cool stuff.&lt;/li&gt;&#xA;&lt;li&gt;I went to Nashville for work. First time. It was fun.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;February&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I went to Portland, OR to meet my grandson. He was born in September. Also first time in PDX. Neat place.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;May&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I created &lt;a href=&#34;https://github.com/bit101/wire&#34;&gt;Wire&lt;/a&gt; - a graphics toolkit.&lt;/li&gt;&#xA;&lt;li&gt;I created &lt;a href=&#34;https://github.com/bit101/lures&#34;&gt;Lures&lt;/a&gt; - a strange attractor toolkit.&lt;/li&gt;&#xA;&lt;li&gt;I rebooted &lt;a href=&#34;https://artfromcode.com&#34;&gt;Art From Code&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;I had shoulder surgery for a torn rotator cuff tendon. Five weeks in a sling 24/7. Six months of PT.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;June&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I bought a &lt;a href=&#34;https://www.midifighter.com/shop/midi-fighter-twister&#34;&gt;MIDI Fighter Twister&lt;/a&gt;. My favorite piece of MIDI equipment. I wrote a &lt;a href=&#34;https://github.com/bit101/sc_mft&#34;&gt;SuperCollider class&lt;/a&gt; for it which I use a lot.&lt;/li&gt;&#xA;&lt;li&gt;Lots of pieces created on &lt;a href=&#34;https://artfromcode.com&#34;&gt;Art From Code&lt;/a&gt; using &lt;a href=&#34;https://github.com/bit101/wire&#34;&gt;Wire&lt;/a&gt; and &lt;a href=&#34;https://github.com/bit101/lures&#34;&gt;Lures&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;August&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Started running again after surgery.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;October&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I turned 60. 😱&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;November&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I created and published a high res, longish video along with the musical soundtrack for it. &lt;a href=&#34;https://www.youtube.com/watch?v=7FdjKPHs7BA&#34;&gt;See it here!&lt;/a&gt; This was a long term goal. It&amp;rsquo;s not amazing, but it felt great to get it done.&lt;/li&gt;&#xA;&lt;li&gt;I got a few more small hardware synths. Having great fun jamming with them.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;December&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Learned that I have yet another grandchild on the way.&lt;/li&gt;&#xA;&lt;li&gt;Been pretty obsessed with music making technology. Code, hardware and software synths, DAWs, effects plugins.&lt;/li&gt;&#xA;&lt;li&gt;Got Covid for the first time. Spent New Years Eve and Day in bed, miserable.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;thoughts&#34;&gt;Thoughts&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been really into music creation stuff in the past few months. It&amp;rsquo;s been cycling in and out for the last couple years, but each time I go a bit deeper and understand a bit more. My initial goal was just to create soundscapes to go with longer animations. But now I kind of want to explore music on its own. I feel like I&amp;rsquo;ve been way to deep in the technology of it all. I need to focus on actually completing some short pieces. &lt;a href=&#34;https://www.jonathanmann.net/jamuary&#34;&gt;#Jamuary&lt;/a&gt; is on and people are posting good stuff. I might try to throw out a few thing here and there. I like the idea of doing quick jams, recording them and putting them on line somewhere. Not as a polished thing, but just - &amp;ldquo;hey, I did this&amp;rdquo;. It&amp;rsquo;s the closest to the visual &amp;ldquo;experiments&amp;rdquo; stuff that I&amp;rsquo;ve always done well with.&lt;/p&gt;&#xA;&lt;p&gt;I also love doing and will continue to do visual creative coding. But honestly in the past few years it often feels like I&amp;rsquo;m going over the same ground over and over. I mean, I&amp;rsquo;ve been doing this for 26 years. I think I do it better now. And I occasionally find something brand new to experiment with. And often combining one technique with another I find something absolutely brand new (at least to me) - something I&amp;rsquo;ve never thought of doing or ever seen anyone else do. But it&amp;rsquo;s not the same as the early 2000s when &lt;em&gt;everything&lt;/em&gt; was brand new to me. That&amp;rsquo;s where music is at for me now.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m pretty sure I&amp;rsquo;ll get back into running again. I started up again a couple months after the surgery, but around October when I got sick (minor cold), I got knocked off my schedule again. I ran last week - then later that day I got sick with Covid! As soon as I&amp;rsquo;m recovered from that, I&amp;rsquo;ll be back out there.&lt;/p&gt;&#xA;&lt;p&gt;This year will see my youngest daughter graduate from college - CS degree at Boston University. I hope the economy improves a little bit. I know it&amp;rsquo;s rough out there for software engineers looking for jobs. And I&amp;rsquo;ll see my second grandson (from my older daughter).&lt;/p&gt;&#xA;&lt;p&gt;Yeah, I&amp;rsquo;m in my 60s now. That just seems so weird. When I was in my late 20s, approaching 30, I started obessing about my own mortality. Oddly, the closer I actually get to it, the less and less worried about it I actually am. My biggest concern now it making sure that I actually enjoy the rest of my time on this merry go round and do meaningful stuff, not work myself to the bone and die chained to the machine. Sounds morbid, but it&amp;rsquo;s not. A while back I got this memento mori coin.&lt;/p&gt;&#xA;&lt;img alt=&#34;memento mori coin&#34; class=&#34;percent50&#34; src=&#34;https://bit-101.com/blog2024_images/2025-01-02/coin.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;One side says &amp;ldquo;Memento Mori&amp;rdquo; - remember death. The other side is &amp;ldquo;Memento Vivere&amp;rdquo; - remember life.&lt;/p&gt;&#xA;&lt;p&gt;Just as a reminder that I&amp;rsquo;m not going to live forever, so I should be living my life with meaning. And hey! I have a lot of years left, hopefully! Although my grandson is just a year old, no reason I couldn&amp;rsquo;t get the title of great-grandfather eventually.&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t do resolutions per se, but I really feel the need to work on reducing stress and living happier in the coming year. The past couple years have been super stressful. For six and a half years - from mid 2017 to early 2024, I did not get sick. Not even a cold. This year, I&amp;rsquo;ve had two serious illnesses, one minor cold and a surgery. I know that stress is a key factor. For the last few weeks I was thinking to myself that stress was going to make me sick. Sure enough. It&amp;rsquo;s mostly work stuff, and I resolve to fix it. Because this cannot continue.&lt;/p&gt;&#xA;&lt;p&gt;I guess that&amp;rsquo;s about it. Not all rosy, but the new year is always a good cue to start fresh and make improvements. I&amp;rsquo;m in.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Journal</title>
      <link>https://bit-101.com/blog/posts/2024-12-28/journal/</link>
      <pubDate>Sat, 28 Dec 2024 12:07:31 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-12-28/journal/</guid>
      <description>&lt;p&gt;Over the years I&amp;rsquo;ve made many abortive attempts to keep some sort of journal. For no other reason than I think it&amp;rsquo;s good to reflect on your life on a regular basis in some kind of organized way. I love writing and find that by describing something in written words often helps me to clarify things. I&amp;rsquo;ve always maintained that teaching is the best way to learn something deeply. So maybe by writing things that are going on in my life, I&amp;rsquo;ll learn something. Better than disorganized worrying about them anyway.&lt;/p&gt;&#xA;&lt;p&gt;Inspired by &lt;a href=&#34;https://mastodon.social/@stepschwarz/113725077187513233&#34;&gt;this post on mastodon&lt;/a&gt;, I decided to give it another shot.&lt;/p&gt;&#xA;&lt;h2 id=&#34;ok-how-to-do-it&#34;&gt;OK, how to do it?&lt;/h2&gt;&#xA;&lt;p&gt;Choices:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Pen and paper or digital? Digital.&lt;/li&gt;&#xA;&lt;li&gt;In the cloud or local? Local first, but backed up somewhere, somehow, securely.&lt;/li&gt;&#xA;&lt;li&gt;Some service or DIY? A mix. I&amp;rsquo;m OK using a tool that makes it easier, but no proprietary formats. No lock in.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;So we come down to the two universal tools - the only two things anyone needs ever. vim and git.&lt;/p&gt;&#xA;&lt;p&gt;Disclaimer: I actually use neovim, but generally don&amp;rsquo;t make the distinction. It will be important here because I use neovim&amp;rsquo;s lua syntax for some of the stuff below.&lt;/p&gt;&#xA;&lt;p&gt;I found a few articles that had a lot of valuable tips. Here are some good ones:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://danishpraka.sh/posts/journaling-in-vim/&#34;&gt;https://danishpraka.sh/posts/journaling-in-vim/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://james-lemin.com/code/2020/05/29/keeping_a_journal_in_vim.html&#34;&gt;https://james-lemin.com/code/2020/05/29/keeping_a_journal_in_vim.html&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;What&amp;rsquo;s great about using vim for stuff like this is that you have all kinds of configurations, tools, plugins, autocommands, etc. to make it do what you want it to do and look how you want it to look. The articles listed above have some great ideas on templates (vim skeletons) and autocommands and autocompletion sources. I&amp;rsquo;ve used a few of these and will add more as time goes on.&lt;/p&gt;&#xA;&lt;h2 id=&#34;enter-vimwiki&#34;&gt;Enter vimwiki&lt;/h2&gt;&#xA;&lt;p&gt;While I was looking around for tips, I heard mention of &lt;a href=&#34;https://github.com/vimwiki/vimwiki&#34;&gt;vimwiki&lt;/a&gt;. I knew of this project but I didn&amp;rsquo;t know that it had a built-in diary mode. This sets up a section of your vimwiki to be a journal, exactly what I wanted. I already have another solution for my personal wiki knowledge base, so I don&amp;rsquo;t use the wiki part of vimwiki, but the diary part looked great.&lt;/p&gt;&#xA;&lt;p&gt;Essentially, there are a handful of vim mappings (keyboard shortcuts) it sets up. For example, one takes you to your diary post for today (creating that file if it does not exist). If I have a thought, I just hit that shortcut, I&amp;rsquo;m in my journal entry for today, I write what I want, save and exit.&lt;/p&gt;&#xA;&lt;p&gt;There are other shortcuts for yesterday&amp;rsquo;s entry and tomorrow&amp;rsquo;s, as well as the diary index file. And another shortcut when you are in the index to create a nicely formatted list of links to all your entries. Also, while you&amp;rsquo;re in any diary entry, you can tab to each link and hit enter to go to that page. And backspace to go back to the previous page. All very nice added functionality.&lt;/p&gt;&#xA;&lt;h2 id=&#34;templates&#34;&gt;Templates&lt;/h2&gt;&#xA;&lt;p&gt;Off to a great start, but now when I go to today&amp;rsquo;s entry, if I haven&amp;rsquo;t already written anything, I get a blank document. I had a template in mind - things that I want to comment on each day. Prompts, you could say. I came up with something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Personal ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Reading ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Watching ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Health ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Work ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One thing I discovered is that vimwiki uses wiki markdown flavor. I&amp;rsquo;m used to &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;##&lt;/code&gt;, &lt;code&gt;###&lt;/code&gt; for headers and &lt;code&gt;-&lt;/code&gt; for unordered lists. But wiki flavor favors &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;===&lt;/code&gt; and &lt;code&gt;*&lt;/code&gt;. I think it will work fine with the other format, but when I viewed my files in github, they were all messed up. I guess github was seeing the &lt;code&gt;.wiki&lt;/code&gt; extensions and trying to render them with that flavor, and it was not good. So I use what I&amp;rsquo;m supposed to.&lt;/p&gt;&#xA;&lt;p&gt;Now, to use this template every time I make a new entry. In vim, you have various &amp;ldquo;autocommands&amp;rdquo; that run on a file in various situations, like when it is created, opened, closed, before and after writing, saving, etc. We can tap into these to apply the template. Here&amp;rsquo;s the start.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vim.api.nvim_create_autocmd(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;BufNewFile&amp;#34;&lt;/span&gt;, {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pattern &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*/vimwiki/diary/**&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  callback &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; journalCreate,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a bit different than default vimscript, because neovim uses a lua syntax for it&amp;rsquo;s config. Actually, it can use either, but I moved over to lua a while ago.&lt;/p&gt;&#xA;&lt;p&gt;So I&amp;rsquo;m creating an autocommand that will run when a new file is created. It&amp;rsquo;s limited to files created in &lt;code&gt;*/vimwiki/diary/&lt;/code&gt; which is where vimwiki puts its diary files. When a new file is created in vim in that location it runs the &lt;code&gt;journalCreate&lt;/code&gt; function, which I also created.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;journalCreate&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  date &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; vim.fn.expand(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;%:t:r&amp;#39;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vim.api.nvim_command(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;0r ~/.config/nvim/templates/diary.template&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vim.api.nvim_buf_set_text(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, {&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;= &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;..&lt;/span&gt; date &lt;span style=&#34;color:#555&#34;&gt;..&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34; =&amp;#34;&lt;/span&gt;})&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;end&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, vimwiki creates the file with a name like &lt;code&gt;2024-12-28.wiki&lt;/code&gt;. I get and expand the file name with &lt;code&gt;%&lt;/code&gt; and limit it to the tail of the full path - &lt;code&gt;:t&lt;/code&gt; and strip off the extension - &lt;code&gt;:r&lt;/code&gt;. This gives me a &lt;code&gt;date&lt;/code&gt; variable like &lt;code&gt;2024-12-28&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Next, I run a vim command of &lt;code&gt;0r&lt;/code&gt; with the path to the template I just showed above. To be honest, I have no idea what &lt;code&gt;0r&lt;/code&gt; means, but I know what it does. It loads that template and yeets it into the buffer that was just created.&lt;/p&gt;&#xA;&lt;p&gt;Finally, I set the text in buffer 0 (current buffer), line 0, row 0 to line 0, row 0, to the date, surrounted with &lt;code&gt;=&lt;/code&gt; chars.&lt;/p&gt;&#xA;&lt;p&gt;Now, when I go to a yet-uncreated entry, I get something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;= 2024-12-28 =&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Personal ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Reading ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Watching ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Health ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;== Work ==&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;* &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that I added a couple of blank lines to the top of my template to make room for the header insertion.&lt;/p&gt;&#xA;&lt;p&gt;That whole file parsing, date header thing is more than just cosmetic though, as we&amp;rsquo;ll see in a minute.&lt;/p&gt;&#xA;&lt;h2 id=&#34;automating-the-index&#34;&gt;Automating the index&lt;/h2&gt;&#xA;&lt;p&gt;As I said, one of the main vimwiki diary shortcuts takes you to the diary index page. By default this is empty. I want it to be an actual index with links to all my entries. This might be a case for another template, but we don&amp;rsquo;t even need that because vimwiki already has a function for generating such an index. It&amp;rsquo;s set up with another shortcut, but I don&amp;rsquo;t want to have to hit that shortcut or wonder whether or not the index is up to date when I open it. I just want it to be up to date as soon as I open it.&lt;/p&gt;&#xA;&lt;p&gt;Time for another autocommand!&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vim.api.nvim_create_autocmd(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;BufReadPost&amp;#34;&lt;/span&gt;, {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pattern &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*/vimwiki/diary/diary.wiki&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  command &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;VimwikiDiaryGenerateLinks&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This one activates when I read a new file, but only matches my diary index file. When this happens it calls a vimwiki-defined function, &lt;code&gt;VimwikiDiaryGenerateLinks&lt;/code&gt;. So the first thing that happens after I open this file is that it gets updated. Perfect.&lt;/p&gt;&#xA;&lt;p&gt;By the way, here&amp;rsquo;s what it looks like:&lt;/p&gt;&#xA;&lt;img alt=&#34;diary index&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-12-28/diary-index.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Note that each entry has the name given in the header of the corresponding file. If I did not generate that header, each list item would just say &amp;ldquo;Personal&amp;rdquo; as that&amp;rsquo;s the first header on the page - unless I manually added another header. Hence the automation.&lt;/p&gt;&#xA;&lt;p&gt;In github, this is at least human readable. Sadly, the wiki-type links do not become linkable there. Maybe there&amp;rsquo;s some github config for that? Not a big deal though.&lt;/p&gt;&#xA;&lt;img alt=&#34;diary index&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-12-28/github-diary.png&#34; /&gt;&#xA;&#xA;&lt;h2 id=&#34;a-shortcut&#34;&gt;A shortcut&lt;/h2&gt;&#xA;&lt;p&gt;Two things remaining.&lt;/p&gt;&#xA;&lt;p&gt;One, I don&amp;rsquo;t want to have to run vim and then hit a shortcut to get to my diary. I wanted to have a command that would just open vim to the diary index.&lt;/p&gt;&#xA;&lt;p&gt;Simple enough with an alias.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;diary&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;vim ~/vimwiki/diary/diary.wiki&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;backups&#34;&gt;Backups&lt;/h2&gt;&#xA;&lt;p&gt;Finally, I wanted to be able to back up my diary, not just have it be subjected to accidental deletion or eventual hard drive failure. Since it&amp;rsquo;s all just text, github makes sense. I made a repo and got it all tracking my vimwiki. But I didn&amp;rsquo;t want to have to manually do &lt;code&gt;git add .&lt;/code&gt;, &lt;code&gt;git commit -m &amp;lt;message&amp;gt;&lt;/code&gt;, &lt;code&gt;git push&lt;/code&gt; every time. Because I know I&amp;rsquo;ll forget and get lazy.&lt;/p&gt;&#xA;&lt;p&gt;My first thought was a cron job, set to push every hour or whatever. This all worked fine except for the push. The key I use for git has a passphrase and there&amp;rsquo;s no good way I found to get the cron job to use that. Most advice came down to set up a new key without a passphrase. Nope.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;This section has had a few changes. Don&amp;rsquo;t get attached to anything you read here. A bunch will change by the end of this post. Leaving the trail here for future reminders though.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;I came up with an easier solution, just adding on to my alias.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;alias&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;diary&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;vim ~/vimwiki/diary/diary.wiki &amp;amp;&amp;amp; ~/diarycommit&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This opens the diary in vim and when it closes, it runs a script, &lt;code&gt;diarycommit&lt;/code&gt; (not actually located at &lt;code&gt;~&lt;/code&gt;). Here&amp;rsquo;s what that looks like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;#!/bin/sh&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#366&#34;&gt;cd&lt;/span&gt; ~/vimwiki&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;date&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git push&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This adds, commits - using the current date/time as a commit message - and pushes.&lt;/p&gt;&#xA;&lt;p&gt;The result: I type &lt;code&gt;diary&lt;/code&gt;, write what I want, close vim, it&amp;rsquo;s backed up.&lt;/p&gt;&#xA;&lt;p&gt;This isn&amp;rsquo;t perfect. I might go into my diary and leave it open for while, or start editing some other files in vim. It won&amp;rsquo;t actually commit and push until I finally close vim.&lt;/p&gt;&#xA;&lt;p&gt;Also, I might be editing something else in vim and have a thought, use a shortcut to open the current page, and close it and go on doing what I was doing. In this case, because vim wasn&amp;rsquo;t opened with the alias, my changes won&amp;rsquo;t get pushed when I do eventually close vim.&lt;/p&gt;&#xA;&lt;p&gt;But it&amp;rsquo;s good enough for now. Eventually I&amp;rsquo;ll open things as described above. Or I can always just run &lt;code&gt;diarycommit&lt;/code&gt; manually.&lt;/p&gt;&#xA;&lt;p&gt;If anyone has any tips on how to use a passphrase from a cron job, let me know.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m also thinking of an autocommand that runs the commit script on buffer close.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: this works! See updates below.&lt;/p&gt;&#xA;&lt;h2 id=&#34;updates&#34;&gt;Updates&lt;/h2&gt;&#xA;&lt;h3 id=&#34;one&#34;&gt;One&lt;/h3&gt;&#xA;&lt;p&gt;I had one issue where if I was ever editing some non-wiki markdown file (like this very post I was writing via Hugo), vim was treating it like it was part of vimwiki, adding vimwiki mappings to it and other things that were messing me up. &lt;a href=&#34;https://vimwiki.github.io/vimwikiwiki/Tips%20and%20Snips.html&#34;&gt;I found the solution here&lt;/a&gt;&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Vimwiki considers every markdown-file as a wiki file&lt;/p&gt;&#xA;&lt;p&gt;Vimwiki has a feature called &amp;ldquo;Temporary Wikis&amp;rdquo;, that will treat every file with configured file-extension as a wiki. To disable this feature add this to your vimrc:&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;let g:vimwiki_global_ext = 0&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;Alternative you can set vimwiki to use markdown syntax but a different file-extension, like the default .wiki.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This did the trick.&lt;/p&gt;&#xA;&lt;h3 id=&#34;two&#34;&gt;Two&lt;/h3&gt;&#xA;&lt;p&gt;I figured out something better for git saves. Yet another autocommand!&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vim.api.nvim_create_autocmd(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;BufWritePost&amp;#34;&lt;/span&gt;, {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pattern &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;*/vimwiki/diary/**&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  command &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;[[ silent! !/path/to/diarycommit ]]&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This executes right after I write a buffer to a file (save it). Only if it&amp;rsquo;s in my diary files. It just runs the commit script. Save the file, it gets commited. Works great! I added &lt;code&gt;silent!&lt;/code&gt; here so it didn&amp;rsquo;t give the whole commit message in vim. But you can leave it out too.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Hello, again!</title>
      <link>https://bit-101.com/blog/posts/2024-12-28/hello/</link>
      <pubDate>Sat, 28 Dec 2024 11:21:24 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-12-28/hello/</guid>
      <description>&lt;p&gt;I felt the urge to write a blog post today. When I started to do so, I happened to look at the list of posts in my Hugo site repo and saw that the first post on this &amp;ldquo;new&amp;rdquo; blog was exactly one year ago today. So happy birthday static blog!&lt;/p&gt;&#xA;&lt;h2 id=&#34;static-site-for-bit-101-2017-2023&#34;&gt;Static Site for BIT-101 2017-2023&lt;/h2&gt;&#xA;&lt;p&gt;Speaking of static sites and this &amp;ldquo;new&amp;rdquo; site, there were two other versions of this blog before the one I started a year ago. The original started in 2003 and went until 2017. That got huge and unwieldy and I created a new one in 2017. Both of these were in Wordpress and I&amp;rsquo;ve kept them both up and somewhat functional ever since under at &lt;a href=&#34;https://bit-101.com/2003&#34;&gt;bit-101.com/2003&lt;/a&gt; and &lt;a href=&#34;https://bit-101.com/2017&#34;&gt;bit-101.com/2017&lt;/a&gt;. But&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;For one, the stuff going on around Wordpress these days is a bit ridiculous. I already distanced myself from WP for my own personal reasons around usability and functionality. But I want to even further distance myself from it now.&lt;/p&gt;&#xA;&lt;p&gt;More than that though, even a dead Wordpress site takes a non-trivial amount of effort. Maintaining the install, keeping up to date with updates, not only of WP itself, but any plugins you have installed, maintaining the database. If I want to eventually switch hosts, I&amp;rsquo;ll need to migrate all that, which is not my idea of a good time.&lt;/p&gt;&#xA;&lt;p&gt;So I went ahead and moved the 2017-2023 site over to a fully static Hugo site. There are a few projects that help with this. I found this one:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/SchumacherFM/wordpress-to-hugo-exporter&#34;&gt;https://github.com/SchumacherFM/wordpress-to-hugo-exporter&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This installs as a Wordpress plugin. Then you go to the plugin and tell it to export all your stuff. It took a few minutes and then prompted me to download a zip file which was a bare bones Hugo site setup, with every post as a markdown file, and a folder named &lt;code&gt;wp-content/uploads&lt;/code&gt; which contained all the images and other assets I&amp;rsquo;d uploaded to my WP site. From downloading the plugin, figuring out how it works, to getting the site download was maybe twenty minutes. Not bad.&lt;/p&gt;&#xA;&lt;p&gt;Now I needed a theme. I copied over the theme from this blog and did some tweaking to everything to get it set up like I wanted. And the new static site worked&amp;hellip; kind of.&lt;/p&gt;&#xA;&lt;h3 id=&#34;problems&#34;&gt;Problems&lt;/h3&gt;&#xA;&lt;p&gt;There were a lot of places where I&amp;rsquo;d hardcoded things to &lt;code&gt;bit-101.com/blog&lt;/code&gt; - especially for images and other assets. This was actually already a problem as soon as I moved the site over to &lt;code&gt;bit-101.com/2017&lt;/code&gt; and I had made some progress on fixing these one by one via Wordpress. But now that I had all the markdown files in a folder on my local machine, it was pretty easy to just run &lt;code&gt;sed&lt;/code&gt; and fix all of these in pretty much one fell swoop.&lt;/p&gt;&#xA;&lt;p&gt;There were also just a bunch of dead links. Again, having a folder full of markdown files made this easy to check. I used this node.js tool: &lt;a href=&#34;https://github.com/tcort/markdown-link-check&#34;&gt;https://github.com/tcort/markdown-link-check&lt;/a&gt; and it worked great. Those are all cleaned up. Fixed where possible, removed otherwise.&lt;/p&gt;&#xA;&lt;p&gt;There were some formatting weirdnesses. Like Wordpress makes apostrophes into some funky HTML entity that I didn&amp;rsquo;t like so I &lt;code&gt;sed&lt;/code&gt;ed those back into simple single quote characters. A lot of the code blocks are still a mess of &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; tags instead of triple quoted blocks. I&amp;rsquo;ll probably go back and clean those up at some point.&lt;/p&gt;&#xA;&lt;p&gt;Another problem was that the exported &lt;code&gt;wp-content/uploads&lt;/code&gt; folder seemed to be missing a few files. Rather than messing with that too much, I just copied over the same folder from the original Wordpress install that was already on my server. This had everything.&lt;/p&gt;&#xA;&lt;p&gt;With that, the newly staticized site was up and running! Markdown and config checked into git. Assets backed up. No more WP updates, plugin updates, database updates, etc. to worry about. Migrating to a new host will just take updating and running my deploy script.&lt;/p&gt;&#xA;&lt;h3 id=&#34;next-up&#34;&gt;Next up&lt;/h3&gt;&#xA;&lt;p&gt;Next up will be the 2003-2017 site. This is a LOT bigger though, and will have way more broken links to fix and update. But I&amp;rsquo;ll probably do the initial export some time soon and get that out of the way.&lt;/p&gt;&#xA;&lt;p&gt;And then there&amp;rsquo;s my &lt;a href=&#34;https://archived.artfromcode.com&#34;&gt;Art From Code&lt;/a&gt; archived site. I did the same thing this year, where I moved the Wordpress version to a subdomain and created a Hugo site. I&amp;rsquo;ll staticize the archived one eventually too.&lt;/p&gt;&#xA;&lt;h3 id=&#34;search&#34;&gt;Search&lt;/h3&gt;&#xA;&lt;p&gt;Another thing I&amp;rsquo;d like to do with these static sites is to implement search. The 2017 site has a lot of content and the 2003 site has I think something like 1200-1300 posts. Hugo has nothing for built in search, but there are some projects that will help. Generally I think it&amp;rsquo;s a matter of&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Building an index of content on the site.&lt;/li&gt;&#xA;&lt;li&gt;Creating the functionality to search this content.&lt;/li&gt;&#xA;&lt;li&gt;Creating a UI to enable the search and display the results.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Another thing to look into in the coming year.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Evenly Placed Points on Bézier Curves</title>
      <link>https://bit-101.com/blog/posts/2024-09-29/evenly-placed-points-on-bezier-curves/</link>
      <pubDate>Sun, 29 Sep 2024 10:03:15 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-09-29/evenly-placed-points-on-bezier-curves/</guid>
      <description>&lt;h2 id=&#34;intro&#34;&gt;Intro&lt;/h2&gt;&#xA;&lt;p&gt;This is something I need to solve every now and then - the need to interpolate smoothly along a Bézier curve. This can manifest in a few ways.&lt;/p&gt;&#xA;&lt;p&gt;One, you want to evenly place points along the curve.&lt;/p&gt;&#xA;&lt;p&gt;Two, you want to smoothly move an object along the curve.&lt;/p&gt;&#xA;&lt;p&gt;Three, you want to find a point that is some percentage of the way along the curve.&lt;/p&gt;&#xA;&lt;p&gt;I should note that as far as I know there is no mathematical way to perfectly accomplish this. Or at least not one that&amp;rsquo;s feasible to implement in code. So we have to resort to other usable methods.&lt;/p&gt;&#xA;&lt;h2 id=&#34;naive-attempt&#34;&gt;Naive attempt&lt;/h2&gt;&#xA;&lt;p&gt;Well, you might think, the formula for a Bézier curve has a &lt;code&gt;t&lt;/code&gt; value in it, where if &lt;code&gt;t&lt;/code&gt; is 0.0 it&amp;rsquo;s the start of the curve, and at &lt;code&gt;t&lt;/code&gt; = 1.0, it&amp;rsquo;s the end of the curve. So just interpolate &lt;code&gt;t&lt;/code&gt; from 0 to 1 and create a point on each returned location.&lt;/p&gt;&#xA;&lt;p&gt;For this post, I&amp;rsquo;m going to show you some of the code in &lt;a href=&#34;http://github.com/bit101/bitlib&#34;&gt;my own personal geometry library&lt;/a&gt;. It&amp;rsquo;s written in Go and has a lot of dependencies on other code in the library, but I think you&amp;rsquo;ll be able to understand what&amp;rsquo;s going on.&lt;/p&gt;&#xA;&lt;p&gt;I have a function called &lt;code&gt;geom.BezierPoint&lt;/code&gt; which looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Golang&#34; data-lang=&#34;Golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;BezierPoint&lt;/span&gt;(t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p0&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p1&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p2&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p3&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;oneMinusT&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1.0&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;m0&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;oneMinusT&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;oneMinusT&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;oneMinusT&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;m1&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3.0&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;oneMinusT&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;oneMinusT&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;m2&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3.0&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;oneMinusT&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;m3&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&lt;/span&gt;Point{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;m0&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p0.X&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;m1&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p1.X&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;m2&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p2.X&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;m3&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p3.X,&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;m0&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p0.Y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;m1&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p1.Y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;m2&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p2.Y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;m3&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p3.Y,&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Give it your four control points and a &lt;code&gt;t&lt;/code&gt; value and it gives you back a point for that location along the curve.&lt;/p&gt;&#xA;&lt;p&gt;So I&amp;rsquo;ll create some control points on an 800x800 surface and stroke a Bézier curve, then loop through 100 &lt;code&gt;t&lt;/code&gt; values and draw a point at each location.&lt;/p&gt;&#xA;&lt;img alt=&#34;uneven points on a Beizer curve&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-09-29/figure_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;OK, so we do indeed have 100 points along the curve, but they are not evenly spaced. At the start and at the end they are further apart, while in the center, they&amp;rsquo;re very cramped. Not what was specified at all.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-better-approach-but-still-bad&#34;&gt;A better approach, but still bad&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve solved this in the past. And when I wanted to add this solution to my library, I attempted the same solution. And it works. Kind of.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-concept&#34;&gt;The concept:&lt;/h3&gt;&#xA;&lt;p&gt;You start by figuring out how much space you want between each point. Say it&amp;rsquo;s 20 pixels.&lt;/p&gt;&#xA;&lt;p&gt;You get the first point on the curve, the &lt;code&gt;p0&lt;/code&gt; control point.&lt;/p&gt;&#xA;&lt;p&gt;Now you get additional points by increasing &lt;code&gt;t&lt;/code&gt; in very small increments until the distance from &lt;code&gt;p0&lt;/code&gt; to the point you get is very close to 20 pixels.&lt;/p&gt;&#xA;&lt;p&gt;Then you make this new point the starting point and continuing to increase &lt;code&gt;t&lt;/code&gt; in tiny increments until you get another point that&amp;rsquo;s about 20 pixels from that.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-problems&#34;&gt;The problem(s)&lt;/h3&gt;&#xA;&lt;p&gt;The first problem is that if you don&amp;rsquo;t increase &lt;code&gt;t&lt;/code&gt; in small enough increments, you may not get very close to the target distance. So you have to check if you&amp;rsquo;ve gone beyond that distance. Then your points just aren&amp;rsquo;t super accurate. They&amp;rsquo;re kind of evenly spaced, but there&amp;rsquo;s still an occasional glitch where they are a bit further apart of closer together.&lt;/p&gt;&#xA;&lt;p&gt;The solution to the first problem is to use very tiny increments for &lt;code&gt;t&lt;/code&gt; so that you are always getting very close to the target distance. This solution introduces the second problem. I found I sometimes had to use increments as low as 0.0001 to get the errors down to a sub-pixel level. That&amp;rsquo;s 10,000 iterations to create a set of just 100 evenly placed points. This can get SLOW.&lt;/p&gt;&#xA;&lt;p&gt;One thought is that for a low number of points you can get away with a higher increment and for a higher number of points, you need a much lower increment. I spent some time trying to figure out a formula that would account for that, without much luck.&lt;/p&gt;&#xA;&lt;p&gt;The third problem is that even when you use low increments, the last point may be very close to the end of the path, less than the target distance. So you still get some glitchiness there.&lt;/p&gt;&#xA;&lt;p&gt;Yet another problem is that this relies on knowing how much space you want between points. But say you just know that you want 100 points along the curve. Now you need to know the length of the curve. And that comes back to the same performance problem. The simplest way to get the length is to generate a bunch of Bézier points and add up the distances between each of them. But the more points you create, the more accurate the length. You may have to add a few thousand points to get a decently accurate length. This adds to the low performance problem.&lt;/p&gt;&#xA;&lt;p&gt;I spent a lot of time trying to add error correction/propagation and other strategies to make this more performant and still look good. No real joy there.&lt;/p&gt;&#xA;&lt;p&gt;So I went back to the drawing board.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-much-better-approach&#34;&gt;A much better approach!&lt;/h2&gt;&#xA;&lt;p&gt;Rethinking this, I realized I could break the Bézier curve down into a segmented path and then interpolate along that path.&lt;/p&gt;&#xA;&lt;p&gt;Before I even thought of this in terms of Bézier paths though, I needed a function that can interpolate along such a path. A path is simply a list of points. And in my library I have such an object, called &lt;code&gt;PointList&lt;/code&gt;. This function should take a &lt;code&gt;t&lt;/code&gt; value from 0 to 1 and return a point somewhere along this path, where 0 would be the first point, 1 would be the last point, anything else, somewhere in between, along one of the segments of that path.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the Go code I came up with. Again, you might not understand all of what&amp;rsquo;s going on here if you&amp;rsquo;re not familiar with the language, but you should be able to suss out the basics.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Golang&#34; data-lang=&#34;Golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;PointList)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;Interpolate&lt;/span&gt;(t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;error&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;||&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;nil&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;errors.&lt;span style=&#34;color:#c0f&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;t should be from 0.0 to 1.0&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;==&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p.&lt;span style=&#34;color:#c0f&#34;&gt;First&lt;/span&gt;(),&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;nil&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;==&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p.&lt;span style=&#34;color:#c0f&#34;&gt;Last&lt;/span&gt;(),&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;nil&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;length&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p.&lt;span style=&#34;color:#c0f&#34;&gt;Length&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;accumulated&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;len&lt;/span&gt;(p)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;p0&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p[i]&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;p1&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;]&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;l&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p0.&lt;span style=&#34;color:#c0f&#34;&gt;Distance&lt;/span&gt;(p1)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(accumulated&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;l)&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;length&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;tmin&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;accumulated&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;length&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;tmax&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(accumulated&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;l)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;length&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;Norm&lt;/span&gt;(t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tmin,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;tmax)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;LerpPoint&lt;/span&gt;(t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p0,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p1),&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;nil&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;accumulated&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;l&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;nil&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;errors.&lt;span style=&#34;color:#c0f&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;unable to interpolate&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In short, the whole path has a length, which is the sum of all the segments between each of its points. First, I look for the segment that contains &lt;code&gt;t&lt;/code&gt; - where the percentage of the length so far is less than &lt;code&gt;t&lt;/code&gt;, but after this segment it&amp;rsquo;s greater than &lt;code&gt;t&lt;/code&gt;. Then I interpolate between the two points that make up that segment and return that point.&lt;/p&gt;&#xA;&lt;p&gt;In this animation, I create a path of completely random points and then interpolate &lt;code&gt;t&lt;/code&gt; from 0 to 1, drawing a point for that &lt;code&gt;t&lt;/code&gt; on each frame. You can see it smoothly moves along the path.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;smoothly interpolated path&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-29/anim_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Alternately, I can just increase &lt;code&gt;t&lt;/code&gt; by 0.01 to create 100 evenly placed points.&lt;/p&gt;&#xA;&lt;img alt=&#34;smoothly interpolated points on a path&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-09-29/figure_03.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;back-to-béziers&#34;&gt;Back to Béziers&lt;/h3&gt;&#xA;&lt;p&gt;Now we just need to create a segmented path like this for a Bézier curve. That&amp;rsquo;s easy enough. Just go back to our naive approach from the start of this post and create a bunch of points with the &lt;code&gt;BezierPoint&lt;/code&gt; function. For demonstration purposes, first I&amp;rsquo;ll do it in a lower resolution so you can see the segment&amp;rsquo;s clearly.&lt;/p&gt;&#xA;&lt;img alt=&#34;a Bézier curve converted to segments&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-09-29/figure_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here, I just increased &lt;code&gt;t&lt;/code&gt; by 0.1 each iteration, creating 10 segments.&lt;/p&gt;&#xA;&lt;p&gt;Now this is a &lt;code&gt;PointList&lt;/code&gt; - a path made by discrete points - that I can interpolate over with my &lt;code&gt;PointList.Interpolate&lt;/code&gt; function. This time I&amp;rsquo;ll draw the actual Bézier curve, and then 100 interpolated points.&lt;/p&gt;&#xA;&lt;img alt=&#34;a Bézier curve with interpoated points&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-09-29/figure_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Not great. The points are spaced nicely, but they don&amp;rsquo;t follow the curve too well. Because the path I created was too low res. Let&amp;rsquo;s try making that path with 100 segments instead of just 10.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s see what this segmented path looks like with 100 segments.&lt;/p&gt;&#xA;&lt;img alt=&#34;a Bézier curve made with 100 segments&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-09-29/figure_06a.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s pretty smooth. You can&amp;rsquo;t really tell this is made from straight lines. It just looks like a curve.&lt;/p&gt;&#xA;&lt;p&gt;Now let&amp;rsquo;s see what happens when we evenly interpolate 100 points along this path. This time with the actual smooth curve drawn in the background.&lt;/p&gt;&#xA;&lt;img alt=&#34;a Bézier curve with interpolated points&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-09-29/figure_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Woohoo! That&amp;rsquo;s pretty much perfect. The points follow the curve. They are evenly spaced. Start and end points are all good.&lt;/p&gt;&#xA;&lt;p&gt;But one question remains:&lt;/p&gt;&#xA;&lt;p&gt;How many segments to create for a given curve and number of points? Well, as you can see above, for 100 interpolated points, 100 segments worked out pretty well. In my final function, I just doubled that to be sure. That&amp;rsquo;s still only 200 segments, as compared to up to 10,000 iterations in my earlier attempt, and with even better results.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the function I came up with:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Golang&#34; data-lang=&#34;Golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;UniformBezierPoints&lt;/span&gt;(count&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p0,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p1,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p2,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p3&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;PointList&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;path&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;NewPointList&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;count&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(count&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;BezierPoint&lt;/span&gt;(t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p0,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p1,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p2,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p3)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;path.&lt;span style=&#34;color:#c0f&#34;&gt;Add&lt;/span&gt;(p)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;NewPointList&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;count;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;count&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;p,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;_&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;path.&lt;span style=&#34;color:#c0f&#34;&gt;Interpolate&lt;/span&gt;(t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Add&lt;/span&gt;(p)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pretty simple actually, with the &lt;code&gt;Interpolate&lt;/code&gt; function doing a lot of the heavy lifting.&lt;/p&gt;&#xA;&lt;p&gt;Note that these points technically still aren&amp;rsquo;t perfect, Most of them will not perfectly lie on the curve. But by segmenting that curve enough, they&amp;rsquo;re close enough for anyone&amp;rsquo;s eye. And if that isn&amp;rsquo;t close enough, segment the curve some more.&lt;/p&gt;&#xA;&lt;h3 id=&#34;another-example&#34;&gt;Another example&lt;/h3&gt;&#xA;&lt;p&gt;Earlier I said that one application of this would be smoothly moving an object along a Bézier curve.&lt;/p&gt;&#xA;&lt;p&gt;In this final animation, I have a Bézier curve. The red dot is moving along the curve using the normal Bézier curve formula. Note that it does not move at a constant speed. It starts out fast, slows in the middle and speeds up again at the end. The green dot moves at a constant speed along the curve. Also note that they both finish together. I guess you could use this kind of thing for a custom easing function if the existing ones aren&amp;rsquo;t enough for you.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;points moving along a bezier curve&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-29/anim_07.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;Hopefully this helps someone somewhere down the line. I&amp;rsquo;ll probably continue to improve this code myself. So the code you see here may look a bit different than what you see in my library. I&amp;rsquo;ll probably create an separate &lt;code&gt;BezierCurve&lt;/code&gt; object with methods that do these things. And a function to create a segmented path from a set of Bézier control points. And&amp;hellip; who knows what else.&lt;/p&gt;&#xA;&lt;p&gt;[Update] I did end up creating that &lt;code&gt;BezierCurve&lt;/code&gt; object. You can see it here: &lt;a href=&#34;https://codeberg.org/bit101/bitlib/src/branch/main/geom/bezier.go&#34;&gt;https://codeberg.org/bit101/bitlib/src/branch/main/geom/bezier.go&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Goodbye Obsidian</title>
      <link>https://bit-101.com/blog/posts/2024-09-08/goodbye-obsidian/</link>
      <pubDate>Sun, 08 Sep 2024 10:50:30 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-09-08/goodbye-obsidian/</guid>
      <description>&lt;p&gt;Back in 2020, I set up a &lt;a href=&#34;https://www.bit-101.com/2017/2020/09/git-based-wiki/&#34;&gt;personal &amp;ldquo;wiki&amp;rdquo; on github&lt;/a&gt;. It was one of the most useful personal productivity things I ever did. Every time I installed something or made some change to one of my computers, it went in the wiki. Every time I got some new device and set it up, into the wiki. All the dates and costs of all my subscription services were there. References I wanted to keep track of, books I&amp;rsquo;d read, on and on. The best part of it is that it just became a habit. And it was very simple. Just markdown pages linking to each other. It was on line, so available anywhere, but I could also easily have a local copy on any device and sync the changes with &lt;code&gt;git push&lt;/code&gt; and &lt;code&gt;git pull&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;You can see in the comments of the above post that many people suggested I try &lt;a href=&#34;https://obsidian.md/&#34;&gt;Obsidian&lt;/a&gt; for this purpose. That post also wound up on HackerNews and a lot of there were plenty of &amp;ldquo;Duh, this guy is dumb. Just use Obsidian, loser&amp;rdquo; type comments. (I&amp;rsquo;m exaggerating, but that was the sentiment.)&lt;/p&gt;&#xA;&lt;p&gt;In a reply to one of those post comments I wrote:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&amp;ldquo;yeah, I&amp;rsquo;ve tried it. lots of bells and whistles, but I prefer the simplicity of vim and github.&amp;rdquo;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;However, I eventually gave in and got deeper into Obsidian. I read all the articles and tutorials. I watched all the videos from all the knowledge management gurus.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Side note: Obsidian and some of these other similar apps and systems have become almost semi-religions. &lt;em&gt;Zettelkasten&lt;/em&gt; and &lt;em&gt;Build a Second Brain&lt;/em&gt; come to mind. Look those up at your own risk! Yes, there are blogs and whole YouTube channels devoted to this stuff, but there are also books on this stuff. And courses and workshops and personal coaching and foundation memberships you can pay hundreds or thousands of dollars for - each for just one individual school of knowledge management. I read the posts and watched the videos, but I didn&amp;rsquo;t join any of the cults.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I think I used Obsidian for close to a couple of years, but more recently I&amp;rsquo;ve stopped using it and I&amp;rsquo;m going back to my old github based wiki.&lt;/p&gt;&#xA;&lt;h3 id=&#34;reasons-for-leaving&#34;&gt;Reasons for leaving&lt;/h3&gt;&#xA;&lt;h4 id=&#34;non-portability&#34;&gt;Non-portability&lt;/h4&gt;&#xA;&lt;p&gt;Obsidian is often touted as being just pure markdown, like I already had, but with more features built in. The truth is that when you start to use those extra features, you start moving away from pure markdown. And this immediately makes your data less portable.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;One example is &lt;a href=&#34;https://www.mediawiki.org/wiki/Help:Links&#34;&gt;media-wiki type links&lt;/a&gt;. These do indeed make it easier to create links. In fact, you can create a link to a non-existent page and then click on that link and the page will be created. But these types of links will be useless in other markdown systems, like github. When I ported things back over to github, I had to go through a lot of manual link fixing. I later found that there were automated tools that will transform your Obsidian formatted data to pure markdown. But the fact that you need such a tool points up the fact that once you start buying into Obsidian, you start to become locked in. It may take some work to export your data and move it somewhere else.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Another is the fact that Obsidian has a large base of plugins designed to make your data more usable. Some of these just affect how you view your data, or manage it, but some of them require you to write special non-markdown syntax. In fact, there&amp;rsquo;s a whole query language type thing where you can write a block of &amp;hellip; code basically, that pulls in data from all over your documents. It&amp;rsquo;s super powerful, but completely useless outside of Obsidian. And for a lot of this stuff, there&amp;rsquo;s no way to export it into pure markdown because it&amp;rsquo;s all dynamic queries to live data.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Obsidian also has a lot of reliance on &amp;ldquo;front matter&amp;rdquo; which is metadata in a special format at the start of a file. I&amp;rsquo;m pretty sure this is not standard markdown.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Of course, you don&amp;rsquo;t have to use any of these non-pure-markdown type features. But that&amp;rsquo;s where all the Obsidian gurus will be pointing you at, in order to get the full power of the tool.&lt;/p&gt;&#xA;&lt;h4 id=&#34;syncing&#34;&gt;Syncing&lt;/h4&gt;&#xA;&lt;p&gt;Obsidian is an app that runs on your computer. Or on your phone or tablet. It creates a local data file where it stores all your data. But what if you want to see the same data on multiple computers or on your computer and on your phone or tablet. Now you need to sync somehow.&lt;/p&gt;&#xA;&lt;p&gt;And Obsidian has you covered here! But Obsidian syncing is a paid service, and it is not cheap. As of this writing, it&amp;rsquo;ll cost you $48 USD a year for one repo, 1GB storage, one month history. For $96, you get 10 repos, a 12 month history and 10GB (or 100GB for $192 a year). The lowest plan is not horrible, but honestly I&amp;rsquo;m just software-subscriptioned out at this point.&lt;/p&gt;&#xA;&lt;p&gt;So I decided to make my own sync system. How hard could it be? Turns out, a little bit hard, but let&amp;rsquo;s walk through it. Obsidian stores all your markdown files in a file tree, and you need to sync the root folder of that to whatever machine you want to use them on. Easy. Dropbox can do that. Syncthing can do that. So could Google Drive or multiple other services.&lt;/p&gt;&#xA;&lt;p&gt;But, it also creates a &lt;code&gt;.obsidian&lt;/code&gt; folder there with a bunch of json files inside it. This all amounts to a kind of manifest of your whole install, all your plugins, preferences, styling, and all your markdown files and their structure. When you change a file, Obsidian notices the change and updates the relevant json files in the &lt;code&gt;.obsidian&lt;/code&gt; folder.&lt;/p&gt;&#xA;&lt;p&gt;So you make some changes on computer A. Obsidian updates the &lt;code&gt;.obsidian&lt;/code&gt; stuff and at the same time your sync service (say Dropbox) starts syncing all the changes. Computer B starts getting changes synced and notices markdown file changes so updates its own &lt;code&gt;.obsidian&lt;/code&gt; content. But at the same time, computer A&amp;rsquo;s &lt;code&gt;.obsidian&lt;/code&gt; content gets synced. And to make it worse, computer B starts syncing it&amp;rsquo;s changes in &lt;code&gt;.obsidian&lt;/code&gt; and now you have two computers trying to sync the same files, quite often with different content. And this causes conflicts. Just like a git conflict, some of your data is rendered useless. I tried all kinds of timing and delays to try to avoid the conflicts, and tried multiple different syncing systems but always got lots of conflicts. Eventually I just excluded the &lt;code&gt;obsidian.md&lt;/code&gt; file from being synced. That eliminated the conflicts, but meant I had to manually set up all my plugins and preferences on each device. Meh. I assume the paid-for syncing service handles this perfectly.&lt;/p&gt;&#xA;&lt;p&gt;Also, not using Obsidian&amp;rsquo;s official sync means you have no history of changes. So I also set up a cron job that pushes the whole folder to a github repo a couple times a day.&lt;/p&gt;&#xA;&lt;h4 id=&#34;obsidian-tries-to-do-everything&#34;&gt;Obsidian tries to do everything&lt;/h4&gt;&#xA;&lt;p&gt;Again this is my own fault for buying into the hype, but yes, Obsidian tries to be the one tool to organize everything in your life. It&amp;rsquo;s your knowledge base, it&amp;rsquo;s your note taking system, it&amp;rsquo;s your planner and calendar, it&amp;rsquo;s your research tool, it&amp;rsquo;s your daily todo list.&lt;/p&gt;&#xA;&lt;p&gt;And yes, I had all the plugins that let me do all this stuff and used it that way a lot. Particularly at work. I wound up having two Obsidian repos. One for my personal stuff, which was mostly my wiki, but also planning and task management and research, etc. And one repo for work, which was mostly task management, but also meeting notes, planning, knowledge base, etc.&lt;/p&gt;&#xA;&lt;h3 id=&#34;back-to-basics&#34;&gt;Back to basics&lt;/h3&gt;&#xA;&lt;p&gt;All of this was fine, but it wound up growing into something I never really wanted. But a lot of people thrive on this stuff. In the reddit Obsidian group, you&amp;rsquo;ll often see people posting images like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;obsidian network graph&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-09-08/network.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Each dot is a page in their Obsidian repo. The lines are where one page links to another one. You can drag this around and it has amazing physics where the dots will repel each other and self-organize into the most efficient shape. Totally neat to watch and play with, but it does absolutely NOTHING to help keep me organized. It actually made me want to add more and more content and more and more links between my pages so I could have a really complex network graph too.&lt;/p&gt;&#xA;&lt;p&gt;I thought back to my earlier comment response: &amp;ldquo;yeah, I&amp;rsquo;ve tried it. lots of bells and whistles, but I prefer the simplicity of vim and github.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;I really started feeling this earlier this year. Mostly because I largely stopped using Obsidian other than for my daily todo list at work. In spite of all the nifty plugins and query languages and visualizations of how all my notes connected to all my other notes, etc. I got to the point where I just didn&amp;rsquo;t even want to open it anymore.&lt;/p&gt;&#xA;&lt;p&gt;So I saved out my personal repo and converted it back to plain markdown and uploaded it back to github. I&amp;rsquo;ve been starting to use it more, but it&amp;rsquo;ll take me a bit of time to fully get back in the habit like I was a couple years ago. That git-based wiki is purely a knowledge base, like it was before. I&amp;rsquo;m happy with this. Almost totally pure markdown. No overly complex syncing system or cron jobs for history management. No conflicts. I love it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-right-tool-for-the-job&#34;&gt;The right tool for the job&lt;/h3&gt;&#xA;&lt;p&gt;Moving my wiki back to being a straight up knowledge base left me with some gaps to fill.&lt;/p&gt;&#xA;&lt;h4 id=&#34;note-taking&#34;&gt;Note taking&lt;/h4&gt;&#xA;&lt;p&gt;What about one-off notes, meeting notes, capturing random ideas or lists of things that I don&amp;rsquo;t necessarily need to save long term? I went through a bunch of things trying to find the best solution, including Google Keep, Simple Note, some self hosted solutions. Then I remembered that a while back I paid for a lifetime license for &lt;a href=&#34;https://getupnote.com/&#34;&gt;Upnote&lt;/a&gt;. I reinstalled it on all my devices (free syncing!!!) and deleted all my ancient notes and started using it on a regular basis. I really like it for this purpose. It has a wonderful UX and it&amp;rsquo;s a joy to use. I know there are other very similar apps out there, so I&amp;rsquo;m not trying to sell you on Upnote, just saying that I&amp;rsquo;m very happy with it.&lt;/p&gt;&#xA;&lt;h4 id=&#34;task-management&#34;&gt;Task management&lt;/h4&gt;&#xA;&lt;p&gt;And we can&amp;rsquo;t forget our venerable todo list! What todo? What todo?&lt;/p&gt;&#xA;&lt;p&gt;I went back to an old standby, &lt;a href=&#34;https://todoist.com/&#34;&gt;todoist&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Honestly, this is the toughest category. Whenever I try a new task management system, I get all excited about it and jam every possible task in there and I&amp;rsquo;m productive as hell for about two weeks. Then I&amp;rsquo;m down to the things I don&amp;rsquo;t really want to do, and I start avoiding it and everything gets backlogged and before long weeks have gone by without me using it. Then I find a new system and the cycle starts over.&lt;/p&gt;&#xA;&lt;p&gt;Todoist isn&amp;rsquo;t anything magical. But it&amp;rsquo;s a solid system. If I&amp;rsquo;m not using it, I can&amp;rsquo;t really blame the app. As I&amp;rsquo;ve said, I&amp;rsquo;ve used it a few times in the past and I&amp;rsquo;m happy with it. I just need to commit to not letting it get stale.&lt;/p&gt;&#xA;&lt;p&gt;Actually, it&amp;rsquo;s going quite well with todoist at work. I got in a pretty good habit with my Obsidian based todo list at work. Honestly, that&amp;rsquo;s all I really used Obsidian for in the past six months. So it was just a matter of switching that habit over to todoist and that&amp;rsquo;s going well. I had a bunch of Obsidian plugins that did things like recurring tasks and rollover/rescheduled tasks, etc. but it was not perfect. Todoist does all this stuff out of the box and does it much better, so I think I&amp;rsquo;ll be fine there.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m also trying to use if more for personal task management and that&amp;rsquo;s somewhat promising. Having the solid habit at work, and using the same app at home in the same way is helping. I actually have two separate todoist accounts - for work and personal. I got my company to pay for the full subscription for the work version, which I mainly wanted so I could have more than five &amp;ldquo;projects&amp;rdquo; which helps split up things into multiple areas. But for personal stuff, the free version is adequate at this point.&lt;/p&gt;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;Anyway, long rant about personal crap. If you actually read this far, I am amazed. I hope this did not come across as too angry. Obsidian really is a remarkable tool. And a lot of people love it and get a lot out of it. It&amp;rsquo;s just not for me, given the reasons given above.&lt;/p&gt;&#xA;&lt;p&gt;The moral is to do whatever works for you and figure out what works for you. Don&amp;rsquo;t buy into the hype of this one particular app or system being so amazing that it will do all things for everybody.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/close-up-of-holding-a-gemstone-11266559/&#34;&gt;Header photo by Alina Vilchenko from Pexels&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Joy of Geometry: Part II</title>
      <link>https://bit-101.com/blog/posts/2024-09-01/joy-of-geometry-part-ii/</link>
      <pubDate>Sun, 01 Sep 2024 05:50:32 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-09-01/joy-of-geometry-part-ii/</guid>
      <description>&lt;p&gt;Others in this series:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-08-31/joy-of-geometry-part-i/&#34;&gt;Part I&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I moved through a bunch more examples. I am now up to figure 1.24. I backfilled some of the ones I missed yesterday, but there are still a few I skipped.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Figure 1.6 - I&amp;rsquo;m just convinced this one is wrong. Or it&amp;rsquo;s at least written incorrectly. Or I am unable to read it correctly.&lt;/li&gt;&#xA;&lt;li&gt;Figure 1.22 - Just too complex. It would not be clear what was happening and would require a bunch of explanation.&lt;/li&gt;&#xA;&lt;li&gt;Figure 1.24 - Same.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Maybe I&amp;rsquo;m just being stupid on the first one and lazy on the other two. Maybe I&amp;rsquo;ll circle back to them later. But here&amp;rsquo;s the rest!&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.12 - This one is pretty interesting, though only applies to equilateral triangles. The text on the bottom is not just static text. It actually does the math on each frame. It really is just exactly 450.00 each time.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;from any point in an equilateral triangle, draw a line perpendicular to each side. The sum of the lengths of these lines will always be the same.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_12.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.13 - There&amp;rsquo;s not much to animate here.&lt;/p&gt;&#xA;&lt;img alt=&#34;a semicircle on one side of an equilateral triangle. lines drawn through points bisecting the side also trisect the semicircle.&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_13.png&#34; /&gt;&#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.16 - You can pause the video and do the math if you want. There may be some rounding errors there.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the distance from the circumcenter to one side of the triangle is one half the distance from the orthocenter to the opposite corner.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_16.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.17 - The &amp;ldquo;center of gravity&amp;rdquo; bit is pretty interesting. I kind of want to cut something out and see if it balances on that point.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the centroid is the intersection of the lines from each corner of a triangle to the middle of the opposite side. it is the center of gravity of the triangle and divides the triangle in to six smaller triangles, each of which have the same area.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_17.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.18 - I was going to say that I couldn&amp;rsquo;t think of a practical use for this kind of thing, then it occurred to me that this relates to a trick for drawing things in perspective.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the line from a corner through the centroid is a median. Red line parallel to one side. Green lines connecting alternate corners. The intersection of those green lines lies on the median.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_18.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.19 - A bit of a no-brainer here.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the median bisects a line parallel to the opposite side.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_19.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.20 - This relationship applies to any concurrent lines in a triangle like this, whether altitudes, medians or bisectors. Maybe others? In the case of medians like this, it&amp;rsquo;s also a bit of a no-brainer, since the median bisects each side and each red/green segment pair is equal.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;Ceva&amp;#39;s therom: if the product of the lengths of the red segments is equal to the product of the lengths of the green segments, then the inner lines are concurrent (doesn&amp;#39;t only apply to medians)&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_20.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.21 - Something about the incircle strikes me as very elegant.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the incenter is the intersection of the bisecting lines from each corner. the incircle is centered on the incenter and touches each side of the triangle.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_21.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.23 - This one feels a bit contrived. &amp;ldquo;A long walk for a short glass of water&amp;rdquo; as they say&amp;hellip; but it looks cool.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;The red line is the bisector of the top corner. the green line is perpendicular to the bisector, passes through another corner. the blue line is formed by two tangent points of the circle and is concurrent with the other two.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-09-01/Figure_1_23.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;</description>
    </item>
    <item>
      <title>Joy of Geometry: Part I</title>
      <link>https://bit-101.com/blog/posts/2024-08-31/joy-of-geometry-part-i/</link>
      <pubDate>Sat, 31 Aug 2024 10:10:20 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-08-31/joy-of-geometry-part-i/</guid>
      <description>&lt;p&gt;Others in this series:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-09-01/joy-of-geometry-part-ii/&#34;&gt;Part II&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Some while back (maybe a couple of years or more ago) I picked up the book, &lt;a href=&#34;https://amzn.to/3Td8DZD&#34;&gt;Joy of Geometry&lt;/a&gt; by Alfred Posamentier.&lt;/p&gt;&#xA;&lt;img alt=&#34;Joy of Geometry&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-08-31/joy_of_geometry.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;I&amp;rsquo;ve dived into it several times, coding up the examples he gives in the book and it&amp;rsquo;s been quite fun. The first chapter is &amp;ldquo;Concurrent Lines&amp;rdquo; and there is a seemingly endless array of examples of relationships that are formed by different parts of geometric shapes - mostly triangles so far.&lt;/p&gt;&#xA;&lt;p&gt;In the past week, I&amp;rsquo;ve been working through these a bit more diligently. The book is 150 pages, and I&amp;rsquo;m only up to page 9, but I&amp;rsquo;ve learned a lot and created several new functions in my geometry library already. I haven&amp;rsquo;t coded up every example from the first nine pages so far, but I&amp;rsquo;ve done the majority. I&amp;rsquo;ve included a bit of explanatory text on each one of them. But I highly recomment actually getting a copy of the book if you&amp;rsquo;re interested in this stuff. There are much deeper explanations of these concepts in the book.&lt;/p&gt;&#xA;&lt;h3 id=&#34;chapter-1-concurrent-lines&#34;&gt;Chapter 1: Concurrent Lines&lt;/h3&gt;&#xA;&lt;p&gt;Note, this is only a fraction of the examples in chapter one. I&amp;rsquo;ll be continuing to work through these and will post more in another post.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figures 1.1, 1.2, 1.3 - The orthocenter.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the three altitudes pass through the orthocenter&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_1-2-3.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.4 - Three non-collinear points will form a unique circle. This is known as the circumcircle of the triangle formed by those three points. The center of that circle is the circumcenter of the triangle.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the bisector of the angle from one corner of the triangle to the orthocenter nd circumcenter also bisects that angle of the triangle.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_4.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.5 - The altitude as a bisector.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;two lines from corners through thre same point on the opposite altitude intersect the opposite sides at two points. the angle formed with the base is bisected by the altitude.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_5.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.7 - The side of a triangle as a bisector.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the altitude is extended and intersects the circumcircle. the segment from the intersection to the orthocenter is bisected by a side of the triangle.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_7.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.8 - A corner of a triangle as a bisector.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;altitudes are extended from two corners and intersect the circumcircle. the third corner bisects the arc formed by these two points.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_8.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.9 - Similar triangles. Introducing the orthic triangle.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;altitudes are extended from all corners and intersect the circumcircle. these form  triangle (green) which is similar to the orthic triangle (red), which is formed from the three altitude feet of the triangle&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_9.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.10 - Relationships with the orthic triangle.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;line segments from the circumcenter to each corner of the triangle are perpendicular to the corresponding sides of the orthic triangle.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_10.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.11 - More on the orthic triangle.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;an infinite number of triangles can be constructed by choosing one point from each side of another triangle. the orthic triangle will be the one with the smallest perimiter.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_11.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.14 - Another magical property of the orthocenter.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a circle formed by the midpoints of two altitudes and the side between them also passes through the orthocenter.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_14.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Figure 1.15 - Congruent circles with the orthocenter.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;the circles formed by any two points of a triangle and the orthocenter (red, green, blue) are congruent to the circumcircle.&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-08-31/Figure_1_15.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;</description>
    </item>
    <item>
      <title>Questioning Sierpinski</title>
      <link>https://bit-101.com/blog/posts/2024-07-17/questioning-sierpinski/</link>
      <pubDate>Wed, 17 Jul 2024 19:55:18 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-07-17/questioning-sierpinski/</guid>
      <description>&lt;p&gt;I had a stray thought about the Sierpinski triangle this morning. A different way of looking at it. I’m under no illusions that I’m the first one to try this idea out, but in the 35+ years I’ve been obsessed with fractals, chaos and strange attractors, I’ve never run across any examples like this, which is surprising, because it’s a very simple idea.&lt;/p&gt;&#xA;&lt;p&gt;I tried the idea out and found it created some very nice patterns, some very Sierpinski-like, and some quite unexpected. So I figured I’d share.&lt;/p&gt;&#xA;&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;&#xA;&lt;p&gt;A quick refresher on the Sierpinski triangle. I assume you all know how it works, so I’ll be brief.&lt;/p&gt;&#xA;&lt;p&gt;You take an equilateral triangle, split it into four equal triangles and remove the center one.&lt;/p&gt;&#xA;&lt;img alt=&#34;level one sierpinski triangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Then you do the same for the three remaining triangles.&lt;/p&gt;&#xA;&lt;img alt=&#34;level two sierpinski triangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And so on, down to the level where the scale is so small you’re not seeing the results anymore.&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You know all this.&lt;/p&gt;&#xA;&lt;h3 id=&#34;question-authority&#34;&gt;Question Authority&lt;/h3&gt;&#xA;&lt;p&gt;My stray thought this morning was, “why do we always remove the center triangle?”&lt;/p&gt;&#xA;&lt;p&gt;What happens if we start removing other triangles instead?&lt;/p&gt;&#xA;&lt;p&gt;Let’s try it. We’ll remove the top triangle.&lt;/p&gt;&#xA;&lt;img alt=&#34;level one sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Note: there&amp;rsquo;s some artifacting here - you can see a faint line between each rendered triangle. Though not initially intended, it kind of works to my advantage, letting you see the different triangles to some degree.&lt;/p&gt;&#xA;&lt;p&gt;OK, fine. Now when we go to the next level, we have that center triangle still. But when we split that up, it’s not going to have a “top triangle”. Well, we can just say that triangle was rotated 180 degrees, so we rotate the algorithm by 180 degrees, and remove the bottom triangle. Now level two looks like this.&lt;/p&gt;&#xA;&lt;img alt=&#34;level two sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The next level&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;level three sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And all the way down (visibly anyway)&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now what happens if instead or removing the top triangle, we always remove the bottom right?&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom right triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Or the bottom left?&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom left triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_09.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;It’s just the same thing, rotated. Makes sense. &lt;/p&gt;&#xA;&lt;p&gt;So far, it’s still vaguely Sierpinski-ish, but&amp;hellip; there&amp;rsquo;s a lot of other stuff going on there.&lt;/p&gt;&#xA;&lt;h3 id=&#34;keep-questioning&#34;&gt;Keep Questioning&lt;/h3&gt;&#xA;&lt;p&gt;We made an easy assumption there when we decided how to deal with the upside-down center triangle. We said it was just rotated 180 degrees. But it could have been rotated 60 degrees to the left or 60 degrees to the right and also appear as an “upside down” triangle. Now our “top”, “bottom left”, bottom right” terminology starts to get confusing. So let’s label the different triangles A, B, C and D. &lt;/p&gt;&#xA;&lt;img alt=&#34;level sierpinski triangle with triangles labeled A, B, C clockwise, with D in the center&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_10.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;In the conventional Sierpinski, we always remove D. So how we rotate the center triangle doesn’t matter. When we’re removing A, B or C, it starts to make a big difference. We have three possibilities, with three different outcomes. We can rotate the center triangle so that A is at the bottom, or B is at the bottom or C is at the bottom. &lt;/p&gt;&#xA;&lt;img alt=&#34;level sierpinski triangle with inner triangles labeled A, B, C clockwise, A being at the bottom&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_11.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;In the example we did above, we had A at the bottom. But let&amp;rsquo;s rotate the triangle 60 degrees clockwise so B is at the bottom like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;level sierpinski triangle with inner triangles labeled A, B, C clockwise, B being at the bottom&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_12.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Removing the top triangle, one iteration looks no different.&lt;/p&gt;&#xA;&lt;img alt=&#34;level one sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_13.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;But two iterations&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;level two sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_14.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Three&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;level three sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_15.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Four&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;level four sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_16.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And finally&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_17.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now we&amp;rsquo;ve got something very different!&lt;/p&gt;&#xA;&lt;p&gt;Now let’s do the same thing, but rotating the center triangle so C is at the bottom.&lt;/p&gt;&#xA;&lt;img alt=&#34;level sierpinski triangle with inner triangles labeled A, B, C clockwise, C being at the bottom&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_18.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And removing the top triangle and rendering that all out&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_19.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;We have the same image, flipped.&lt;/p&gt;&#xA;&lt;p&gt;Just to be clear, we can still remove the bottom right triangle (B) or bottom left (C) and we get the same image rotated:&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom right triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_20.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom left triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_21.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;question-me&#34;&gt;Question Me&lt;/h3&gt;&#xA;&lt;p&gt;Earlier, I said when we had three choices on how to orient the center triangle, I lied.&lt;/p&gt;&#xA;&lt;p&gt;There are three choices if we only rotate. But we can also mirror that triangle as we rotate it. Which gives us 3 more possibilities:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;level sierpinski triangle with inner triangles labeled A, B, C counterclockwise, A being at the bottom&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_22.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;level sierpinski triangle with inner triangles labeled A, B, C counterclockwise, B being at the bottom&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_23.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;level sierpinski triangle with inner triangles labeled A, B, C counterclockwise, C being at the bottom&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_24.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s take the first one first, removing the top triangle.&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_25.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;No difference from what we originally had. Actually, it is flipped on the x-axis, but since it&amp;rsquo;s symmetrical, you can&amp;rsquo;t tell.&lt;/p&gt;&#xA;&lt;p&gt;But when we remove the right or left triangles, we get something brand new.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;full sierpinski triangle with bottom right triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_26.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom left triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_27.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Now we have a bunch of squashed Sierpinskis lined up. Neat!&lt;/p&gt;&#xA;&lt;p&gt;What happens when we put B on the bottom?&lt;/p&gt;&#xA;&lt;p&gt;Removing the top triangle&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_28.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Again we have infinite Sierpinskis lined up, but oriented differently and skewed a bit.&lt;/p&gt;&#xA;&lt;p&gt;Bottom right&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom right triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_29.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Bottom left&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom left triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_30.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;This one goes back to the original again.&lt;/p&gt;&#xA;&lt;p&gt;Finally, for the sake of completeness, we&amp;rsquo;ll do the same with C on the bottom, removing top, bottom right, bottom left:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;full sierpinski triangle with top triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_31.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom right triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_32.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;full sierpinski triangle with bottom left triangle removed&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_33.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;All the same, but differently oriented.&lt;/p&gt;&#xA;&lt;p&gt;OK, we got all six orientations of the center triangle covered. So we&amp;rsquo;re all done, right?&lt;/p&gt;&#xA;&lt;p&gt;Not even close!&lt;/p&gt;&#xA;&lt;h3 id=&#34;question-harder&#34;&gt;Question Harder!&lt;/h3&gt;&#xA;&lt;p&gt;What about each of those three outer triangles? So far we&amp;rsquo;ve been orienting them all the same as the original: A on top, B on the bottom right, C on the bottom left.&lt;/p&gt;&#xA;&lt;p&gt;But each one of those could be oriented in six different ways. That&amp;rsquo;s four triangles, each of which could be oriented six ways. 6x6x6x6, or 1296 possible variations!&lt;/p&gt;&#xA;&lt;p&gt;Not to mention the fact that you can choose which triangle to remove - A, B or C, which triples the possibilities. Of course, many of the iterations will be the same as others, some just mirrored or rotated a bit differently, but there are still quite a few interesting ones.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not going to go through all of them, but here&amp;rsquo;s a few neat ones I found:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_34.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_35.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_36.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_37.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_38.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_39.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_40.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_41.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_42.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_43.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_44.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_45.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_46.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_47.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Some of these are looking curiously like Julia fractals!&lt;/p&gt;&#xA;&lt;h3 id=&#34;question-the-question&#34;&gt;Question the Question&lt;/h3&gt;&#xA;&lt;p&gt;Everything is too stable. We&amp;rsquo;re always removing the same triangle throughout all the iterations of an image. I took just ONE of the examples from the above list and altered it so that on each iteration, it randomly decides which triangle to remove.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_48.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_49.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_50.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;And that&amp;rsquo;s only the start. You could randomly choose which order each triangle is in - in each iteration. Or not randomly - do it according to some scheme. I feel like I&amp;rsquo;ll be exploring this one for a while.&lt;/p&gt;&#xA;&lt;h3 id=&#34;question-the-end&#34;&gt;Question the End&lt;/h3&gt;&#xA;&lt;p&gt;After posting, I kept finding more nice looking ones. Here&amp;rsquo;s a few more.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_51.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_52.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_53.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_54.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_55.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_56.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_57.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_58.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_59.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-07-17/fig_60.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Image Sequence Viewer - isv</title>
      <link>https://bit-101.com/blog/posts/2024-06-28/image-sequence-viewr-isv/</link>
      <pubDate>Fri, 28 Jun 2024 21:18:36 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-06-28/image-sequence-viewr-isv/</guid>
      <description>&lt;p&gt;This is a project I am really happy about. I don’t know if anyone else will ever use it and that’s fine. It scratches a major itch for me and that’s great. I really love crafting my own development tooling so I have an environment that works just like I want it to. In this case, there were no tools that I could find to do what I wanted to do, so I made one. And it works!&lt;/p&gt;&#xA;&lt;h3 id=&#34;background---my-workflow&#34;&gt;Background - my workflow&lt;/h3&gt;&#xA;&lt;p&gt;I create animations. In code. Generally I write the code for a single frame of an animation. This has a &lt;code&gt;percentage&lt;/code&gt; variable passed to it that ranges from 0.0 to 1.0, depending which frame is being rendered. All the animation is based on that single variable and allows me to create perfectly looping animations.&lt;/p&gt;&#xA;&lt;img alt=&#34;code for scene 1 of an animation&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-06-28/code.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I render each frame out as a &lt;code&gt;.png&lt;/code&gt; file in a &lt;code&gt;frames&lt;/code&gt; folder, then run ffmpeg on those frames to output either an animated gif, or more recently, an mp4 video. A single animation can have several hundred frames, and if the code is complex, can take a few minutes to render.&lt;/p&gt;&#xA;&lt;img alt=&#34;frames&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-06-28/sequence.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I don’t want to wait all that time to see what the animation is going to look like. Maybe it’s all wrong and I’ll be able to see that within a few frames, cancel, fix things and re-render.&lt;/p&gt;&#xA;&lt;p&gt;Everything I do is on the command line. I code in neovim, in a terminal, compile my code with the Go toolchain, run ffmpeg to create the animation, which then launches a gif or video viewing program to play the result. So there’s no IDE with automatic previews of what’s going on. But I do have that folder of &lt;code&gt;.png&lt;/code&gt; s sitting there.&lt;/p&gt;&#xA;&lt;p&gt;Early on, if I wanted a preview, I’d open up a new pane in tmux and navigate to the &lt;code&gt;frames&lt;/code&gt; folder, then open the first file there in an image viewer. Eventually I got smart and made a vim mapping, &lt;code&gt;,p&lt;/code&gt; that will auto-launch the image viewer with the first frame. From there I could use the left and right arrows to scroll through each frame and see how things were coming along.&lt;/p&gt;&#xA;&lt;p&gt;This worked great for a while, but it requires holding down the arrow key. And sometimes not enough is rendered, so it’s jumping from the last frame to the first, which is jarring, and I’ll just wait for a while, then go back to holding the key again, until enough frames were rendered to give a good representation of the animation.&lt;/p&gt;&#xA;&lt;p&gt;Not awful, but I had this idea of an application custom build for my use case. So I made it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;enter-isv&#34;&gt;Enter isv&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;isv&lt;/code&gt; is an &lt;em&gt;image sequence viewer&lt;/em&gt;. So you have a sequence of image files that will be eventually compiled into a standalone animation. This lets you play that animation back before it is compiled. It is NOT at all optimized for large images and is not intended to be a general purpose image viewer. But it’s great at playing back image sequences of frames up to a few hundred kb each. This is fine for me because even if I’m doing a higher resolution project, I’ll usually start by rendering it in low res at first to work out all the details, and only switch over to the final version when everything is worked out.&lt;/p&gt;&#xA;&lt;img alt=&#34;ui window showing one frame of a window&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-06-28/isv01.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;usage&#34;&gt;Usage&lt;/h3&gt;&#xA;&lt;h4 id=&#34;playing&#34;&gt;Playing&lt;/h4&gt;&#xA;&lt;p&gt;&lt;code&gt;isv&lt;/code&gt; is meant to be run from the command line, even though it opens up a gui window. So it’s not going to work in a terminal only vm or while sshing into a remote machine. You can either open it up in the directory where your frames are, by typing &lt;code&gt;isv&lt;/code&gt; there, or giving it a path to where those frames are: &lt;code&gt;isv path/to/frames&lt;/code&gt;. It will automatically load and display the first image (&lt;code&gt;png&lt;/code&gt;, &lt;code&gt;jpg&lt;/code&gt; or &lt;code&gt;jpeg&lt;/code&gt;) it finds. You can then use it just like I described using any other image viewer - scrolling back and forth through frames using the left and right arrow keys. When it hits the last frame it will loop around to the first, and vice-versa if going backwards. Other minor features include hitting &lt;code&gt;f&lt;/code&gt; to go to the first frame and &lt;code&gt;l&lt;/code&gt; to go to the last. So far though, that’s no better than what I had.&lt;/p&gt;&#xA;&lt;p&gt;But hit &lt;code&gt;p&lt;/code&gt; and the frames start auto-playing through just like a video player, auto-looping when it gets to the end. Hit &lt;code&gt;p&lt;/code&gt; again, or space, and they stop. Same thing with &lt;code&gt;r&lt;/code&gt; - it will play the frames in reverse. And even better, &lt;code&gt;b&lt;/code&gt; will play in “bounce” mode. The animation will play forward to the last frame, then bounce back and play in reverse to the first frame, over and over. This is really good in the early stages of a render where there are fewer frames. Rather than that jarring jump back to frame one, it bounces forward and back smoothly and is a much better experience. As you get a few seconds into the animation, you may decide to switch it back to regular play mode.&lt;/p&gt;&#xA;&lt;p&gt;By default, &lt;code&gt;isv&lt;/code&gt; attempts to play the frames back at 30 fps. Really just a bit slower than that, since all it really does is put a 33 ms delay in between each frame and doesn’t account for any other overhead. But this is just a preview, so I decided not to overcomplicate it. Using the up and down arrow keys you can increase the speed to 60 fps (aspirational - you very well may never achieve that) or unlimited (no delay), and decrease the speed to 10 fps, 5 fps and 1 fps, in case you want to inspect the details of your animation in slow motion.&lt;/p&gt;&#xA;&lt;p&gt;The status bar will report the current play mode and attempted fps.&lt;/p&gt;&#xA;&lt;img alt=&#34;ui window showing one frame of a window&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-06-28/isv02.png&#34; /&gt;&#xA;&#xA;&lt;h4 id=&#34;watching&#34;&gt;Watching&lt;/h4&gt;&#xA;&lt;p&gt;When &lt;code&gt;isv&lt;/code&gt; launches, it reads the directory and makes a list of the supported image files there. This is what it starts playing through. But if you’re actively rendering the frames of your animation, you will want to update that list from time to time.&lt;/p&gt;&#xA;&lt;p&gt;Hit &lt;code&gt;w&lt;/code&gt;  and it will start refreshing the image list every four seconds. Hit it again to stop watching. Of course this is not really &lt;em&gt;watching&lt;/em&gt;, in the sense of an event-driven system where it knows when the directory contents are changed. It’s just reading the directory again. I may eventually do real file system watching, but it may be overcomplicating things, as it seems to work pretty well as it is.&lt;/p&gt;&#xA;&lt;p&gt;If you want to refresh the list more often, hit the &lt;code&gt;&amp;lt;&lt;/code&gt; key (technically &lt;code&gt;,&lt;/code&gt; since you don’t need to shift). Or to refresh less often, &lt;code&gt;&amp;gt;&lt;/code&gt;  (or &lt;code&gt;.&lt;/code&gt; ). These will set the update interval in one-second intervals from 1-10.&lt;/p&gt;&#xA;&lt;p&gt;The watch state and timing data is also displayed in the status bar.&lt;/p&gt;&#xA;&lt;img alt=&#34;ui window showing one frame of a window&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-06-28/isv03.png&#34; /&gt;&#xA;&#xA;&lt;h4 id=&#34;command-line-flags&#34;&gt;Command line flags&lt;/h4&gt;&#xA;&lt;p&gt;By design, I didn’t want to assume anything when you start &lt;code&gt;isv&lt;/code&gt; , so it doesn’t auto-play or auto-watch by default. But you can do any of that with command line flags.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;-p&lt;/code&gt; will have &lt;code&gt;isv&lt;/code&gt; auto-play when it starts. Likewise &lt;code&gt;-r&lt;/code&gt; and &lt;code&gt;-b&lt;/code&gt; will play in reverse or bounce mode upon startup.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;-w n&lt;/code&gt; will start watching the directory for changes every &lt;code&gt;n&lt;/code&gt;  seconds. i.e. &lt;code&gt;-w 2&lt;/code&gt; will refresh the list every 2 seconds.&lt;/p&gt;&#xA;&lt;p&gt;You can combine these like &lt;code&gt;isv -p -w 2&lt;/code&gt; or even &lt;code&gt;isv -pw 2&lt;/code&gt;. &lt;/p&gt;&#xA;&lt;p&gt;In my vim mapping to preview the currently rendering animation I set it up as &lt;code&gt;isv -bw 2 &amp;amp;&lt;/code&gt; so it starts in bounce mode and refreshes every 2 seconds. The final &lt;code&gt;&amp;amp;&lt;/code&gt; has it run as a background thread so it doesn’t block my vim output.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;isv -h&lt;/code&gt; will of course show the full help file, which includes the command line flags as well as the keyboard shortcuts available while playing.&lt;/p&gt;&#xA;&lt;img alt=&#34;help info in console&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-06-28/help.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;behind-the-scenes&#34;&gt;Behind the scenes&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;isv&lt;/code&gt; was built with fyne (&lt;a href=&#34;https://fyne.io/&#34;&gt;https://fyne.io/&lt;/a&gt;) which is a cross platform gui framework for Go. I found it super simple to get started with and really easy to figure out just about everything I needed to. The only real hard part was the “cross platform” part. Normally cross-compiling Go code is a piece of cake. But since fyne links to external C libraries for native ui elements, it needs access to a C compiler such as gcc or clang even to compile on your current OS. Compiling for a different OS is even more complex. They also have a tool called fyne-cross (&lt;a href=&#34;https://github.com/fyne-io/fyne-cross&#34;&gt;https://github.com/fyne-io/fyne-cross&lt;/a&gt;) which helps, but can get pretty complex in itself. I was able to compile Linux on Linux and Windows on Linux. But I had to compile the MacOS version on a Mac. Even still Apple now quarantines unsigned apps on their new arm64 systems and you can only fix it on the command line, not in the control panel like you used to. (Instructions in the readme in the repo listed below.) So if you want to install a pre-compiled binary on your M1, you’ll have to jump through some hoops. Sorry, but I’m not paying Apple so that you can install my app on your computer.&lt;/p&gt;&#xA;&lt;h3 id=&#34;source&#34;&gt;Source&lt;/h3&gt;&#xA;&lt;p&gt;Of course, this is all open source. You can check out the code and try to build it for yourself, try to install it through the &lt;code&gt;go install&lt;/code&gt;  mechanism, which is pretty sweet, or just grab a binary release:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/bit101/isv&#34;&gt;https://github.com/bit101/isv&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;At this point, I’m pretty much done with development on &lt;code&gt;isv&lt;/code&gt;. I’ll be using it daily in my animation creation and fixing any bugs that might crop up or adding any new features that spring to my mind. But for now it does just what I want.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>June Update</title>
      <link>https://bit-101.com/blog/posts/2024-06-27/june-update/</link>
      <pubDate>Thu, 27 Jun 2024 20:07:53 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-06-27/june-update/</guid>
      <description>&lt;p&gt;Lots going on this past month. I’ve been lax in writing about it, largely because writing long form content has been tough, one-handed.&lt;/p&gt;&#xA;&lt;h2 id=&#34;shoulder&#34;&gt;Shoulder&lt;/h2&gt;&#xA;&lt;p&gt;My last update was about my shoulder surgery. This has been going quite well. I’m just wrapping up week four now and the overall level of pain has been far, far less than I could have expected. I found I didn’t really need the heavy pain killers at all. Mostly got by with ibuprofen and ice. And after a week or so I started only taking that as needed.&lt;/p&gt;&#xA;&lt;p&gt;I won&amp;rsquo;t subject this to you directly, but if you&amp;rsquo;re interested in an internal shot of my shoulder mid-surgery click below. I find it fascinating.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://bit-101.com/blog2024_images/2024-06-27/shoulder.png&#34;&gt;Warning! Internal surgery photo.&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Not being able to move my arm 23+ hours a day has started to make it pretty stiff. I started physical therapy a week and a half ago. While that’s helping range of motion, the combo of the two is making the shoulder a little painful on some days.  But less than one week left in the sling! Honestly, a little nervous to take it off. As annoying as it is, it’s also been a bit of a security blanket. How can I be trusted to use my own arm without something protecting it?&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m sure I&amp;rsquo;ll be fine.&lt;/p&gt;&#xA;&lt;h2 id=&#34;wire-artfromcode-strange-attractors&#34;&gt;Wire, ArtFromCode, Strange Attractors&lt;/h2&gt;&#xA;&lt;p&gt;Last month I wrote a lot about my &lt;code&gt;wire&lt;/code&gt; library &lt;a href=&#34;https://github.com/bit101/wire&#34; title=&#34;https://github.com/bit101/wire&#34;&gt;https://github.com/bit101/wire&lt;/a&gt;. I’ve had a lot of fun creating daily animations for 41 straight days and posting them on &lt;a href=&#34;https://artfromcode.com&#34; title=&#34;https://artfromcode.com&#34;&gt;https://artfromcode.com&lt;/a&gt; . This past week I decided I needed to take a little bit of a break from the daily posts to explore some other ideas, but you&amp;rsquo;ll surely see more &lt;code&gt;wire&lt;/code&gt; pieces in the future.&lt;/p&gt;&#xA;&lt;p&gt;There are two neat updates to &lt;code&gt;wire&lt;/code&gt;. One is the ability to load and save models I&amp;rsquo;ve created and to load in existing 3rd party point cloud models. Currently in terms of 3rd party models it only loads &lt;code&gt;.xyz&lt;/code&gt; files. These were originally created for modeling molecules. It’s a simple text format where each line is an x, y, z vertex, with an optional element name, and some optional metadata at the top, so parsing it was fairly easy. Although it’s not a super common format, there are on-line converters that will take 3d models in many other formats and save them down to &lt;code&gt;.xyz&lt;/code&gt; files. I considered building something to save models in &lt;code&gt;.xyz&lt;/code&gt; format, but decided to create my own format which includes both points and segments. This is how objects are represented in &lt;code&gt;wire&lt;/code&gt;. So you can save and load from that format. There&amp;rsquo;s also the &lt;code&gt;.obj&lt;/code&gt; format which, although it can be way more complex, can be reduced to saving points and edges. This would work, and maybe I&amp;rsquo;ll look into that someday.&lt;/p&gt;&#xA;&lt;p&gt;The other addition to &lt;code&gt;wire&lt;/code&gt; is a new strange attractor library, &lt;code&gt;lures&lt;/code&gt; - &lt;a href=&#34;https://github.com/bit101/lures&#34; title=&#34;https://github.com/bit101/lures&#34;&gt;https://github.com/bit101/lures&lt;/a&gt;. Lures&amp;hellip; attractors&amp;hellip; yeah. This is a super lightweight library. It’s really just a collection of 2d and 3d strange attractor formulas with a mechanism for setting properties and iterating through them to create a series of 2d or 3d points. It doesn’t do any rendering at all, just generates the numbers. But &lt;code&gt;wire&lt;/code&gt; can now generate a model from a lure formula and make a point cloud with as many points as you specify. You’ll see a lot of examples of that in the &lt;a href=&#34;https://artfromcode.com&#34; title=&#34;https://artfromcode.com&#34;&gt;https://artfromcode.com&lt;/a&gt; entries in the past several weeks.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-06-27/20240610.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h2 id=&#34;isv---image-sequence-viewer&#34;&gt;isv - Image Sequence Viewer&lt;/h2&gt;&#xA;&lt;p&gt;This is one of the reasons I wanted to take a break from daily animations. I’ll do a whole post on this, but this is an idea that I’ve been thinking of for a long time and finally decided to sit down and create it. It 100% scratches an itch and I’ve added it to my creative workflow and love it. More to come, but if you want a bit more info&amp;hellip; &lt;a href=&#34;https://github.com/bit101/isv&#34; title=&#34;https://github.com/bit101/isv&#34;&gt;https://github.com/bit101/isv&lt;/a&gt; &lt;/p&gt;&#xA;&lt;h2 id=&#34;music--audio&#34;&gt;Music / Audio&lt;/h2&gt;&#xA;&lt;p&gt;I restarted my exploration into creating music or at least soundscapes to go along with longer creative coding videos. Again using SuperCollider. I bought a little shiny thing that I’ve been eyeing for a long time - a Midi Fighter Twister. I think this will also be worthy of an entire post, along with the library I created for interfacing with it from SuperCollider. &lt;/p&gt;&#xA;&lt;img alt=&#34;midi fighter twister 16-knob midi device&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-06-27/midifightertwister.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;But essentially, it’s a box with 16 midi knobs in a 4x4 arrangement.  But it is amazingly customizable. Each knob can spin infinitely in any direction, so there is no set minimum and maximum orientation. The value for each knob is indicated by a series of white LEDs around the knob. This means that you can dynamically set the value of a knob. The knobs are also push switches and are configurable in four banks, so it’s really like having 64 knobs and 64 buttons. In fact, you can set it up so that pushing and turning acts as a separate control, so you could have 128 knobs. And there are 6 configurable buttons on the sides. And&amp;hellip; well yeah, I’ll make another whole post about this.&lt;/p&gt;&#xA;&lt;p&gt;I spent a few days figuring out exactly how to interface with this thing in SuperCollider and wound up making my own library: &lt;a href=&#34;https://github.com/bit101/sc_mft&#34;&gt;https://github.com/bit101/sc_mft&lt;/a&gt; that lets you access just about all the features through a single object. This has also been really fun. Though I admit I kind of abandoned this over the past few days working on &lt;code&gt;isv&lt;/code&gt;. &lt;/p&gt;&#xA;&lt;p&gt;Eventually there should be some sounds to listen to. As much as I am fine with vomiting out a million images or animations, I&amp;rsquo;m extremely shy about my musical creations.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;I guess that’s about it. Three new repos, each of which I’m pretty excited about. A couple more blog posts to write in the coming days. And a million more ideas floating around in my head. I can’t complain.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Surgery</title>
      <link>https://bit-101.com/blog/posts/2024-05-31/surgery/</link>
      <pubDate>Fri, 31 May 2024 07:53:39 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-05-31/surgery/</guid>
      <description>&lt;p&gt;One month ago &lt;a href=&#34;https://bit-101.com/blog/posts/2024-04-30/i-broke-my-rotation-matrix/&#34;&gt;I posted about my rotator cuff&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;And a few days later &lt;a href=&#34;https://bit-101.com/blog/posts/2024-05-03/mri/&#34;&gt;about my MRI&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Bringing things up to date, spoke to my specialist and surgery was an option. She didn&amp;rsquo;t push it hard, offered other options, and made it clear that surgery was the toughest one to go through. But in discussing it, it was also clear that if I just wanted to deal with it once and for all, surgery was the way to go.&lt;/p&gt;&#xA;&lt;p&gt;Factors I considered:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The amount of pain and discomfort I&amp;rsquo;ve been in on a daily basis.&lt;/li&gt;&#xA;&lt;li&gt;The growing level of disability this was causing: problems driving, eating, dressing, showering, walking the dog, etc.&lt;/li&gt;&#xA;&lt;li&gt;The sleep impact. Almost every morning I&amp;rsquo;ve been awakened with throbbing pain at 3:00 or 4:00 AM, sometimes earlier. Sometimes I can find a better position and get a bit more sleep. Sometimes I struggle and give up and just get up and start my day, maybe try to sneak in a nap later. I think I&amp;rsquo;ve had maybe 2-3 decent nights sleep in the last couple months.&lt;/li&gt;&#xA;&lt;li&gt;The lack of solid gains on previous treatment attempts.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Given all that, I scheduled the surgery and had it yesterday. It&amp;rsquo;s a relatively short outpatient procedure. Started with a nerve block injection, which numbs and paralyzes the whole area. That lasts up to 24 hours. Then general anesthesia. She said if the tendon was torn less than 50% through she&amp;rsquo;d just abrade the edges of the tear so they would heal better. As it turns out, it was a 70% tear. So it was a full repair. This meant drilling a hole and placing an anchor in the bone, then attaching a couple of sutures to the anchor to reattach the tendon. Honestly I&amp;rsquo;m glad she did the full repair. Also, 2-3 anchors is not uncommon in larger tears, so just one is not bad.&lt;/p&gt;&#xA;&lt;p&gt;Went home with &amp;ldquo;the Cadillac of slings&amp;rdquo;. It&amp;rsquo;s huge with a memory foam pillow to keep my arm in the right position and has an integrated exercise ball to keep my hand and wrist bust. I&amp;rsquo;ll be wearing this 24/7 for the next five weeks. So I&amp;rsquo;m glad that it&amp;rsquo;s pretty comfortable - at least it feels like that now.&lt;/p&gt;&#xA;&lt;img alt=&#34;me in a sling after getting home from surgery&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-05-31/sling.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;A couple different painkillers, but as of this writing the nerve block has just pretty much worn off. Zero pain all night and so far only minor discomfort this morning.&lt;/p&gt;&#xA;&lt;p&gt;I took off work for a week and a half, so I can just relax and heal and get used to doing everything left-handed. Typing is slooooow.&lt;/p&gt;&#xA;&lt;p&gt;So Day zero. Post-day one beginning.&lt;/p&gt;&#xA;&lt;p&gt;I would like to add that absolutely every single person I dealt with at the hospital was amazing. Friendly, explained everything, answered every question I had and checked for more. Health care workers sometimes get a bad rap and I imagine in some places they are under a lot of pressure and that might sometimes affect how they deal with people. But they are heros.&lt;/p&gt;&#xA;&lt;p&gt;Funny anecdote: as I was waking up from the anesthesia, I had the distinct impression that there were three beds lined up together, with three of me - one in each bed. Later, when a nurse asked how I was doing I almost told her that I thought I was doing good because there was only one of me. It briefly seemed very important, but then I realized at best it would get an amused laugh.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Art From Code</title>
      <link>https://bit-101.com/blog/posts/2024-05-27/art-from-code/</link>
      <pubDate>Mon, 27 May 2024 09:35:38 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-05-27/art-from-code/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;Many years ago I started a site called Art From Code. You can see the archived version at &lt;a href=&#34;https://archive.artfromcode.com&#34;&gt;archive.artfromcode.com&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I’ve updated this site in fits and starts but (as I described in the intro to this blog) Wordpress had just become not fun anymore. So I made yet another Hugo site, moved the old Wordpress site to a subdomain and made the new one live. The new site:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://artfromcode.com&#34;&gt;artfromcode.com&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;One of the main reasons for this change was a need to better document and preserve what I’m working on. Over the past few years, I’ve done a lot of random experiments and posted a few (or a ton) of images or animations on &lt;em&gt;the site formally represented by a bird&lt;/em&gt;, or more recently on &lt;a href=&#34;https://mstdn.social/@bit101&#34;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Generally I just start a project and riff on it until something cool comes out, post a render, and start riffing some more. The code for any particular image is lost immediately, and the image is only preserved on social media.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-problems&#34;&gt;The Problems&lt;/h2&gt;&#xA;&lt;p&gt;Here are the problems with my workflow up to this point:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;No real curation. I’m just spewing a half dozen images into the void in a single evening. I fear that people might get sick of the firehose. Some are great, but some&amp;hellip; meh. There&amp;rsquo;s a principle in photography that you should take as many pictures as you can, but only publish the really great ones.&lt;/li&gt;&#xA;&lt;li&gt;No record of the outcomes. No gallery or portfolio that I can point to and say I did this piece on this day. Sometimes I’ll also blog things, like in the &lt;a href=&#34;https://bit-101.com/blog/tags/genuary/&#34;&gt;genuary&lt;/a&gt; series. But in general, the pieces go off into social media and (maybe) stay there. But they are mostly undiscoverable after a few weeks.&lt;/li&gt;&#xA;&lt;li&gt;No record of what created those outcomes. The code only shows what created the most recent image. What parameters were used to create the one three iterations earlier? Who knows?&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;the-solution&#34;&gt;The Solution&lt;/h2&gt;&#xA;&lt;p&gt;The new workflow:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I have a &lt;code&gt;sandbox&lt;/code&gt; project which is 100% throwaway code. This is where I can riff to my heart’s content, break things, wipe things out, start over, whatever.&lt;/li&gt;&#xA;&lt;li&gt;When I come up with something that starts to look cool, I copy that code over to a new project in the &lt;code&gt;code&lt;/code&gt; folder. The whole &lt;code&gt;code&lt;/code&gt; folder is under source control.&lt;/li&gt;&#xA;&lt;li&gt;From that location, I continue to refine the piece until it satisfies me.&lt;/li&gt;&#xA;&lt;li&gt;The source code gets checked in.&lt;/li&gt;&#xA;&lt;li&gt;The output gets archived and backed up in a &lt;code&gt;media&lt;/code&gt; folder.&lt;/li&gt;&#xA;&lt;li&gt;I create a post on &lt;a href=&#34;https://artfromcode.com&#34;&gt;Art From Code&lt;/a&gt; with the new piece and schedule it so I’m doing generally one post a day. Some days I don’t do much, but on weekends I might crank out half a dozen pieces and schedule them. I like to stay ahead. This all happens in the &lt;code&gt;site&lt;/code&gt; folder.&lt;/li&gt;&#xA;&lt;li&gt;On the day the piece goes live on the site, I post to Mastodon.&lt;/li&gt;&#xA;&lt;li&gt;I’ve also been posting to a new &lt;a href=&#34;https://www.tumblr.com/blog/artfromcode&#34;&gt;Tumblr blog&lt;/a&gt;, as a secondary source of discovery.&lt;/li&gt;&#xA;&lt;li&gt;And I&amp;rsquo;m starting to think again about longer form videos with audio. Those are in the &lt;code&gt;longform&lt;/code&gt; folder.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;img alt=&#34;file system&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-05-27/filesys.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;more-problems&#34;&gt;More Problems&lt;/h3&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a lot of pieces to this: creating the new project, copying over the sandbox code into the new project, uploading the assets to the site, archiving the assets, creating the blog post and publishing that, committing the code and post to source control, posting to Mastodon, posting to Tumblr. &lt;em&gt;Some&lt;/em&gt; of that is automated. But not nearly enough. It&amp;rsquo;s still a lot of friction, so I&amp;rsquo;ll be working on that.&lt;/p&gt;&#xA;&lt;h2 id=&#34;but&#34;&gt;But&amp;hellip;&lt;/h2&gt;&#xA;&lt;p&gt;It&amp;rsquo;s going well!&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve posted something every day for a while and have more lined up.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not flooding Mastodon with a ton of stuff every day - just the daily scheduled piece. But I still do post some random explorations there - nothing that I feel the need to save, just random interesting experiments.&lt;/p&gt;&#xA;&lt;p&gt;The pieces that I am doing have way more thought and effort put into them. There is often a point where something looks OK and I probably would have posted it to Mastodon, but I feel like it&amp;rsquo;s not really good enough to be the official daily piece. So I work it over until I&amp;rsquo;m really happy with it. Occasionally I&amp;rsquo;ve even abandoned a particular piece when I couldn&amp;rsquo;t quite get it to where I wanted it. So my standards are raised.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve upped my dependency management game. I have several self-created libraries I use for most of my work. In Go, you can import a specific version of a library officially from its published location. But you can also just &lt;code&gt;use&lt;/code&gt; a library by pointing to the local file system location of its source code. Doing this, you get whatever code happens to be in that repo at that particular time. This is super useful for experimenting. If something in my library doesn&amp;rsquo;t work just the way I want it to, or it&amp;rsquo;s missing a function, I can just add or update that function and it&amp;rsquo;s instantly available to the project. But this is a horrible practice for finished pieces because you have no idea what version of the library that piece was created with. This has gotten my to link each daily project to a specific version of each library. If I need to add or change something in a lib, I update its version and update the project to use that version. A bit more friction, but well worth it.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Wire: Part IV - Points</title>
      <link>https://bit-101.com/blog/posts/2024-05-18/wire-part-iv-points/</link>
      <pubDate>Sat, 18 May 2024 19:15:28 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-05-18/wire-part-iv-points/</guid>
      <description>&lt;p&gt;Points are a core data type in the wire library. Every shape has a list of 3d points, which generally make up paths by joining up pairs of points in segments.&lt;/p&gt;&#xA;&lt;p&gt;But rendering points all by themselves is also an interesting technique. All you have to do is call &lt;code&gt;shape.RenderPoints(radius)&lt;/code&gt;. This loops through the point array, projecting each 3d point to a 2d point and drawing a circle of the given radius at that location. Of course, that radius is also scaled via perspective, so the final rendered radius will vary depending on the point&amp;rsquo;s z value. Here&amp;rsquo;s an example. I can create a sphere and draw all its segments by calling &lt;code&gt;sphere.Stroke()&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a wireframe sphere&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;With the same sphere, I can just call &lt;code&gt;RenderPoints&lt;/code&gt; instead and get this:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere rendered with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Here, I put a heavy blur on to make the points show up a bit better.&lt;/p&gt;&#xA;&lt;p&gt;You can even do strokes and points:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere rendered with points and lines&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;You can also just create an empty shape and add points to it:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shape&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;wire.&lt;span style=&#34;color:#c0f&#34;&gt;NewShape&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;5000&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;shape.&lt;span style=&#34;color:#c0f&#34;&gt;AddXYZ&lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;FloatRange&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;),&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;FloatRange&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;),&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;FloatRange&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;),&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a box filled with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;In fact, there&amp;rsquo;s a method to do that even more easily:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shape&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;wire.&lt;span style=&#34;color:#c0f&#34;&gt;NewShape&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;5000&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;shape.&lt;span style=&#34;color:#c0f&#34;&gt;AddRandomPointInBox&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;600&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;600&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;600&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But why stop with boxes? There&amp;rsquo;s a similar method for creating random points in a sphere. But I went one further than that and just made a method that lets you say how many random points you want in that sphere and it does it all for you. Just specify the radius of the sphere and the number of points.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shape&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;wire.&lt;span style=&#34;color:#c0f&#34;&gt;RandomInnerSphere&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;5000&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere filled with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_05a.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This leads us in an obvious direction - what about random points ON a sphere? Got you covered:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shape&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;wire.&lt;span style=&#34;color:#c0f&#34;&gt;RandomSurfaceSphere&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;5000&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere with random points on its surface&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_06.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h3 id=&#34;some-code&#34;&gt;Some Code?&lt;/h3&gt;&#xA;&lt;p&gt;I want to take a quick break and look at some of this sphere code because it&amp;rsquo;s super useful even if you don&amp;rsquo;t use this library. How do you create random points on the surface of a sphere? It&amp;rsquo;s a harder question than it seems at first glance. A naive method would be generate a point at an x, y, z of &lt;code&gt;radius, 0, 0&lt;/code&gt;, then rotate it around the y-axis at a random angle between 0 and 2 * PI. Then rotate it around the z-axis at a random angle from -PI to +PI. Or something like that. But that will leave a concentration of points at the poles, rather than having them evenly distributed. Other methods will leave other artifacts.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve done this a number of times and always spend too much time looking it up. So I made sure I bookmarked a good reference for it this time.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://mathworld.wolfram.com/SpherePointPicking.html&#34;&gt;https://mathworld.wolfram.com/SpherePointPicking.html&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;And here&amp;rsquo;s the actual code that does it in the lib (yes, some room for optimization here, now that I look at it!):&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;RandomPointOnSphere&lt;/span&gt;(radius&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;u&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;FloatRange&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;Angle&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// returns a number from 0 to 2*PI&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;x&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Sqrt&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;u&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;u)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Cos&lt;/span&gt;(t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Sqrt&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;u&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;u)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Sin&lt;/span&gt;(t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;z&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;u&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;NewPoint&lt;/span&gt;(x&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;radius,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;radius,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;z&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;radius)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Getting a random point INSIDE a sphere is another interesting problem. You might try multiplying the &lt;code&gt;radius&lt;/code&gt; value by a random value from 0 to 1. But if you do that, here&amp;rsquo;s what you get:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere filled with random points, largely clustered in the center of the sphere&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_07.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;While the points ARE evenly distributed along the radius, they wind up being more cramped near the center of the sphere, and more sparse near the outer edge. It&amp;rsquo;s a neat effect in itself, but if you want an even distribution, you have a problem. The solution is to multiply &lt;code&gt;radius&lt;/code&gt; by the &lt;del&gt;square&lt;/del&gt; &lt;em&gt;cube&lt;/em&gt; root of a random value from 0 to 1. (See note at end of post.) This will have the effect of adding a bias to larger radii, evening out the visual distribution.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;RandomPointInSphere&lt;/span&gt;(radius&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;u&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;FloatRange&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;Angle&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;x&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Sqrt&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;u&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;u)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Cos&lt;/span&gt;(t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Sqrt&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;u&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;u)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Sin&lt;/span&gt;(t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;z&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;u&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;radius&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Pow&lt;/span&gt;(random.&lt;span style=&#34;color:#c0f&#34;&gt;Float&lt;/span&gt;(),&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1.0&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3.0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;radius&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;NewPoint&lt;/span&gt;(x&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;radius,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;radius,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;z&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;radius)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;yet-another-side-quest&#34;&gt;Yet another side quest&lt;/h3&gt;&#xA;&lt;p&gt;One more note about that problem of clustering points around the poles of a sphere, I actually run into the same thing rendering a non-random sphere. Look at the first couple of animations at the top of this post and you&amp;rsquo;ll see what I mean. The rings at the two poles have the same number of points in them as the points on the equator, so the poles render a bit more bright than the rest of the sphere. I could use the same trick I used here for random points, but after actually trying it, I found that it looks worse. It feels like the poles are artificially sparse. But the bright poles look kind of normal. To me anyway. So I&amp;rsquo;m keeping it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;where-were-we&#34;&gt;Where were we?&lt;/h3&gt;&#xA;&lt;p&gt;There are other random point functions for creating random points on or in a cylinder or a torus. And I&amp;rsquo;ll probably continue to add other similar functions for other random point placements.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a cylinder with points on its surface and one filled with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_08.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a torus with points on its surface and one filled with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_09.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;The code for these is interesting too, but I&amp;rsquo;ll leave it to you to examine the code if you so desire.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s one more challenge on some of these that I&amp;rsquo;ll be tackling soon. It has to do with points on various surfaces. You might notice that on the cylinder example, there are no points on the caps of the cylinder, just on the body. Putting points on the caps is no problem, but how many points to put there? Basically I&amp;rsquo;ll have to figure out the surface area of the body of the cylinder, and then the surface area of each cap itself, then distribute the number of points accordingly. If each cap contributes 10% of the surface of the cylinder, then each one should get 10% of the points, and the body gets 80%.&lt;/p&gt;&#xA;&lt;p&gt;There will be a similar challenge for a box. The left and right sides will get a certain percentage each, top and bottom another percentage, and front and back another. Not rocket science, but I haven&amp;rsquo;t gotten around to coding these cases up yet.&lt;/p&gt;&#xA;&lt;p&gt;A cone presents another set of challenges I haven&amp;rsquo;t even started to think about yet. But it should be interesting.&lt;/p&gt;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;Well that&amp;rsquo;s a bit about rendering 3d points. Hopefully some interesting stuff even if you don&amp;rsquo;t use the library.&lt;/p&gt;&#xA;&lt;h3 id=&#34;note&#34;&gt;Note&lt;/h3&gt;&#xA;&lt;p&gt;Earlier I said that to correctly distribute random points within a sphere, you should take the square root of a random value between 0 and 1, and multiply that by the radius.&lt;/p&gt;&#xA;&lt;p&gt;Thankfully, I was corrected on this point. You use square root for 2d. For a sphere, you&amp;rsquo;ll want to use the cube root. I fixed the text and code and image above. But it&amp;rsquo;s worth looking at the different methods and what they create.&lt;/p&gt;&#xA;&lt;p&gt;Multiplying by a random number:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere filled with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_07.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Multiplying by the square root of a random number:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere filled with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_05.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Multiplying by the cube root of a random number:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere filled with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_05a.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Obviously the last one is &amp;ldquo;correct&amp;rdquo; as far as making an even distribution.&lt;/p&gt;&#xA;&lt;p&gt;In fact, here&amp;rsquo;s an animation moving between a single random number and the 16th root of a random number, which effectively hollows out the sphere. Mmmm&amp;hellip; math!&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;a sphere filled with points&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-18/vid_05b.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;</description>
    </item>
    <item>
      <title>Wire: Part III - Evolution</title>
      <link>https://bit-101.com/blog/posts/2024-05-17/wire-part-iii-evolution/</link>
      <pubDate>Fri, 17 May 2024 08:10:56 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-05-17/wire-part-iii-evolution/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been a bit quiet here, but have been very much heads down working on this &lt;a href=&#34;https://github.com/bit101/wire&#34;&gt;wire&lt;/a&gt; library. I refactored (or more correctly, rewrote) a lot of the core code and added a bunch of new features.&lt;/p&gt;&#xA;&lt;h2 id=&#34;code-changes&#34;&gt;Code Changes&lt;/h2&gt;&#xA;&lt;h3 id=&#34;points-and-paths&#34;&gt;Points and Paths&lt;/h3&gt;&#xA;&lt;p&gt;The biggest fundamental change is in how I store paths. Many objects consist of multiple paths. Consider this torus:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-17/vid_01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Each ring in the torus is a single path composed of a list of points that is stroked. Initially, I stored each path as a separate list of 3d points and then projected those points and stroked a line between each point in the path. This was a good way to get started, but problems cropped up. Imagine a 3d cube. There&amp;rsquo;s no way to draw it in a single path without overlapping, so you get multiple paths, but no matter how you arrange it, you&amp;rsquo;re going to need to use the same point in multiple paths. I have a set of &lt;code&gt;randomize&lt;/code&gt; functions that randomly moves each point to shake up the shape. This meant that those shared points would diverge and the cube would break apart. You might try to share the same actual point object in multiple paths, but then when you transform the shape, you&amp;rsquo;d be transforming some points multiple times.&lt;/p&gt;&#xA;&lt;p&gt;Also, having a list of points as a path turned out to be not so useful. When I draw each path, I scale the width of each line segment in the path and draw it separately to make sure each one is scaled separately. So it&amp;rsquo;s &lt;code&gt;moveto, lineto, stroke, moveto, lineto, stroke, ...&lt;/code&gt; rather than &lt;code&gt;moveto, lineto, lineto, lineto, ..., stroke&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Due to these (and other) issues, I changed each shape to have a single list of points and a single list of segments. A segment connects two points. This was a pretty extensive refactor, but simplified a lot and has proven to be well worth it. In the cube example, there are only eight points, and twelve segments connecting them. Transformations and randomizations just affect those eight points and each segment continues to connect the same two points, so it all works out well.&lt;/p&gt;&#xA;&lt;p&gt;Operations like cloning a shape did become a bit more complex. Rather than just cloning each separate path, I had to clone the list of points, and then make a new segment list that connected the correct pairs of points. It might have been easier if segments contained two indices to points rather than references to point objects, but I&amp;rsquo;m glad I kept it that way because it made other operations easier.&lt;/p&gt;&#xA;&lt;h3 id=&#34;colors-and-z-sorting&#34;&gt;Colors and Z-sorting&lt;/h3&gt;&#xA;&lt;p&gt;I toyed with the idea of having each point contain its own color. This required z-sorting the list of points before drawing them. I got it working for drawing points, but realized that drawing segments this way was going to be more complex. And then, to draw multiple shapes, you&amp;rsquo;d have to pool all the points and segments and z-sort them all together and draw them in order.&lt;/p&gt;&#xA;&lt;p&gt;In the end I decided I liked the constraint of monochrome. So I removed the color and z-sorting code. It might come back at some point, but I&amp;rsquo;m happy with the decision for now.&lt;/p&gt;&#xA;&lt;h3 id=&#34;fog&#34;&gt;Fog&lt;/h3&gt;&#xA;&lt;p&gt;I implemented fog. This just sets a &lt;code&gt;nearFog&lt;/code&gt; and &lt;code&gt;farFog&lt;/code&gt; value and linearly interpolates between them, changing the drawing alpha. Anything with a z value less than or equal to &lt;code&gt;nearFog&lt;/code&gt; is drawn with full opacity. Anything further away than &lt;code&gt;farFog&lt;/code&gt; gets zero opacity. This is a nice effect that was pretty easy to create.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-17/vid_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h3 id=&#34;world&#34;&gt;World&lt;/h3&gt;&#xA;&lt;p&gt;I updated this world singleton so all client operations on it are via methods.&lt;/p&gt;&#xA;&lt;h3 id=&#34;shape-updates&#34;&gt;Shape Updates&lt;/h3&gt;&#xA;&lt;p&gt;I updated a bunch of shapes. In particular, the sphere now has options to draw latitude lines, longitude lines, or both.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-17/vid_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;The cylinder, cone and torus have similar options.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-17/vid_04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-17/vid_05.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-17/vid_06.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I also added a couple of new shapes, grid plane and spring.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-17/vid_07.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Finally, there is now a way to combine shapes using &lt;code&gt;shape.AddShape(otherShape)&lt;/code&gt;. This simply appends the points and segments onto the points and segments of the parent shape. Amazingly simple, but now you have a single, complex shape that you can transform and draw simply. This is one area where I&amp;rsquo;m glad I kept segments containing references to points, not indices to points. Doing it this way means I can just append the new segments to the existing segments, not have to worry about re-indexing them.&lt;/p&gt;&#xA;&lt;p&gt;There are a few other goodies I threw in there as well, but I&amp;rsquo;ll probably cover those later.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-newly-updated-site&#34;&gt;A New(ly updated) Site!&lt;/h2&gt;&#xA;&lt;p&gt;A bit of a teaser here as I&amp;rsquo;ll have another post on this soon, but I updated my &lt;a href=&#34;https://artfromcode.com&#34;&gt;artfromcode.com&lt;/a&gt; site and it has a bunch of neat wire examples there so far. Been working on updating it daily.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Wire: Part II - Rendering</title>
      <link>https://bit-101.com/blog/posts/2024-05-08/wire-part-ii/</link>
      <pubDate>Wed, 08 May 2024 07:27:01 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-05-08/wire-part-ii/</guid>
      <description>&lt;h2 id=&#34;aliasing&#34;&gt;Aliasing&lt;/h2&gt;&#xA;&lt;p&gt;In &lt;a href=&#34;https://bit-101.com/blog/posts/2024-05-07/wire-part-i/&#34;&gt;the first post of this series&lt;/a&gt;, I showed some of the default renders of various 3d shapes. These were rendered with white lines on a black background. Here&amp;rsquo;s how that looked for a cylinder shape:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;no blur cylinder&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/no-blur-cyl.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Not bad, but there&amp;rsquo;s more aliasing of the white lines than I&amp;rsquo;d like. To quote someone I used to work with, it looks &amp;ldquo;crunchy&amp;rdquo;. My libraries use CairoGraphics, which has a few settings for antialiasing. This is on the default. There&amp;rsquo;s also subpixel antialiasing and gray antialiasing (supposed to be good for black and white graphics). Neither of these makes it look much better for me. But let&amp;rsquo;s compare what happens when we turn antialiasing off completely:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;no antialiasing cylinder&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/no-aa-cyl.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Even worse, as expected. All of this is just a natural side effect of drawing very thin, very contrasting lines. But I developed a trick for making these kinds of graphics look pretty cool.&lt;/p&gt;&#xA;&lt;h2 id=&#34;glow-effect&#34;&gt;Glow Effect&lt;/h2&gt;&#xA;&lt;p&gt;It goes like this:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Draw the shape using a thicker stroke than you really want.&lt;/li&gt;&#xA;&lt;li&gt;Apply a strong gaussian blur.&lt;/li&gt;&#xA;&lt;li&gt;Redraw the shape with a narrower stroke.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This gives the graphics a very cool, very customizable glow effect. Step-by-step:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Thick stroke:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;wide stroke cylinder&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/wide-stroke-cyl.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;Blur applied:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;blur only cylinder&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/blur-only-cyl.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;ol start=&#34;3&#34;&gt;&#xA;&lt;li&gt;Thinner stroke on top of the blur:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;blurred cylinder&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/blur-cyl.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;To me, this smooths things out nicely and gets rid of most of the &amp;ldquo;crunchiness&amp;rdquo;. It&amp;rsquo;s subtle here, but you now have three control values to change the effect: the original stroke size, the amount of blur, and the final stroke size. In this example I used an initial stroke of 2 pixels, a blur radius of 10 and a final stroke of 1.&lt;/p&gt;&#xA;&lt;p&gt;In this example, I increased the initial stroke and blur radius quite a bit.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;max blur cylinder&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/max-blur-cyl.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I&amp;rsquo;d actually consider the glow here to be too much, but it&amp;rsquo;s a tuneable effect, so it&amp;rsquo;s good to experiment and see what the parameters do.&lt;/p&gt;&#xA;&lt;h2 id=&#34;inverting-colors&#34;&gt;Inverting Colors&lt;/h2&gt;&#xA;&lt;p&gt;Of course, there&amp;rsquo;s no need to have things always be white on black. We can do black on white:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;no blur cylinder black on white&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/no-blur-cyl-bw.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;And we can apply the same blur effect on that.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;blur cylinder black on white&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/blur-cyl-bw.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I really like this look. I find that when you switch from white on black to black on white, you need to tone down the blur a lot, or it looks muddy.&lt;/p&gt;&#xA;&lt;h2 id=&#34;colors&#34;&gt;Colors&lt;/h2&gt;&#xA;&lt;p&gt;And of course, you can use any colors you want, not just black and white. Here I&amp;rsquo;m using the String object to display the hue value I&amp;rsquo;m using in an HSV color model. And a glow.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;text in hsv&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-08/hsv.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This one gives me a bit of a Blade Runner vibe. Our eyes see different hues differently, so some of the colors aren&amp;rsquo;t very visible on a black background - particularly the blues. You could compensate with wider lines, more glow, etc.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;These are just some of the rendering tricks I&amp;rsquo;ve been using. They&amp;rsquo;re very applicable well beyond this particular library. So hopefully you can try them out on whatever system you&amp;rsquo;re using and get some fun results.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Wire: Part I - Intro</title>
      <link>https://bit-101.com/blog/posts/2024-05-07/wire-part-i/</link>
      <pubDate>Tue, 07 May 2024 14:52:53 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-05-07/wire-part-i/</guid>
      <description>&lt;p&gt;Over the last couple of weeks I&amp;rsquo;ve been working on a somewhat unique 3d library. I call it &lt;code&gt;wire&lt;/code&gt;. It&amp;rsquo;s written in Go and is designed to work with my own &lt;a href=&#34;https://github.com/bit101/blcairo&#34;&gt;blcairo&lt;/a&gt; and &lt;a href=&#34;https://github.com/bit101/bitlib&#34;&gt;bitlib&lt;/a&gt; libraries.&lt;/p&gt;&#xA;&lt;p&gt;What&amp;rsquo;s somewhat unique about this library is that it doesn&amp;rsquo;t support drawing 3d solids or even filled triangles. It&amp;rsquo;s all about drawing 3d lines or paths &amp;hellip; or &lt;em&gt;wires&lt;/em&gt; if you will. It will also draw indiviual 3d points. It also purposely does not do any z-sorting and is really designed to be monocrhomatic so that it doesn&amp;rsquo;t matter what order lines and points are drawn.&lt;/p&gt;&#xA;&lt;p&gt;To get us on the same page on what I mean before we dive in deeper, here&amp;rsquo;s an example.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d sphere&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/sphere-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h2 id=&#34;primitives&#34;&gt;Primitives&lt;/h2&gt;&#xA;&lt;p&gt;There are a small set of primitive objects that everything else is made from.&lt;/p&gt;&#xA;&lt;h3 id=&#34;point&#34;&gt;Point&lt;/h3&gt;&#xA;&lt;p&gt;At the base of everything is a 3d point. This just has an x, y, z position and a bunch of methods that can operate on it. Your usual selection of 3d transform methods and some others, and of course a method that projects the 3d point to a 2d screen coordinate.&lt;/p&gt;&#xA;&lt;h3 id=&#34;pointlist&#34;&gt;PointList&lt;/h3&gt;&#xA;&lt;p&gt;Points then go into point lists. These are used either to create a single path of &amp;ldquo;wires&amp;rdquo; or to render a bunch of individual points. All of the point transform methods also apply to the point lists. So you can rotate, scale, translate, etc. an entire list of points with one command. This follows &lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-20/making-some-points/&#34;&gt;the same pattern I described in an earlier post on 2d points&lt;/a&gt;. And there are methods to render the list of points as a single path or do draw each point individually.&lt;/p&gt;&#xA;&lt;h3 id=&#34;shape&#34;&gt;Shape&lt;/h3&gt;&#xA;&lt;p&gt;One step up from there is a shape. A shape is a list of point lists. Some shapes can be drawn with a single path - a circle for example. Others need multiple paths, like a 3d box - you could do it in a single path, but you&amp;rsquo;d have to retrace lines, which would make some lines darker/brighter. There are multiple ways to solve this. I went with multiple paths. You can also call just about any point method on a shape, which will cascade down and operate on every point in every path in that shape. And then you can draw that shape in the same way. For a quick snippet of what this looks like, here&amp;rsquo;s how you could draw a 3d box.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Golang&#34; data-lang=&#34;Golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;box&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;wire.&lt;span style=&#34;color:#c0f&#34;&gt;Box&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;box.&lt;span style=&#34;color:#c0f&#34;&gt;Rotate&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0.2&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;box.&lt;span style=&#34;color:#c0f&#34;&gt;Stroke&lt;/span&gt;(context)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;3d box&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-05-07/box-01.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;world&#34;&gt;World&lt;/h3&gt;&#xA;&lt;p&gt;Finally, there is a world object. This contains values that help define how things are rendered, like the center point of the world, the perspective, and near and far clipping distances.&lt;/p&gt;&#xA;&lt;h2 id=&#34;predefined-shapes&#34;&gt;Predefined Shapes&lt;/h2&gt;&#xA;&lt;p&gt;Those are the main types of the whole library. Beyond that, there are a bunch of predefined shape objects, including:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Box&lt;/li&gt;&#xA;&lt;li&gt;Circle&lt;/li&gt;&#xA;&lt;li&gt;Sphere&lt;/li&gt;&#xA;&lt;li&gt;Cylinder&lt;/li&gt;&#xA;&lt;li&gt;Cone&lt;/li&gt;&#xA;&lt;li&gt;Pyramid&lt;/li&gt;&#xA;&lt;li&gt;Torus&lt;/li&gt;&#xA;&lt;li&gt;Grid Plane&lt;/li&gt;&#xA;&lt;li&gt;String&lt;/li&gt;&#xA;&lt;li&gt;Torus Knot&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;These are just shape objects with predefined sets of paths in them.&lt;/p&gt;&#xA;&lt;p&gt;The box you&amp;rsquo;ve seen above. But here it is in motion.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d box&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/box-02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;The circle creates a 2d circle that can be transformed in 3d. The sphere, cylinder, cone and torus are all created with a series of circles.&lt;/p&gt;&#xA;&lt;p&gt;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d circle&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/circle-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d sphere&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/sphere-02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d cylinder&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/cylinder-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d cone&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/cone-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d torus&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/torus-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;The pyramid and grid plane, like the box, are drawn with longer straight lines.&lt;/p&gt;&#xA;&lt;p&gt;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d pyramid&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/pyramid-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d grid plane&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/gridplane-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&lt;/p&gt;&#xA;&lt;p&gt;The string object creates vector text objects from a string of characters. I spent a lot of time on this and it was really fun. I&amp;rsquo;ll probably do a whole post just on that.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d text&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/string-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Finally, the torus knot is a complex mathematical structure. It&amp;rsquo;s actually what started this whole project. I ran across the concept of a torus knot and coded it up. That gave me the base point and path types, and the 3d transform and rendering code. This was pretty easy to put together because I&amp;rsquo;ve been hand-coding 3d systems like this for well over 20 years. I&amp;rsquo;m not bragging. Just saying that I keep doing the same things over and over. OK, maybe a humble brag. Anyway, once I had the torus knot looking good, I just started adding other shapes to it.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;3d torus knot&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-05-07/torusknot-01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h2 id=&#34;render-quality&#34;&gt;Render Quality&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll acknowledge that the quality of these animations is a bit aliased. This is just how things look straight out of the box. There&amp;rsquo;s a lot more to cover here, including some tricks I&amp;rsquo;ve been using to make these look a lot better.&lt;/p&gt;&#xA;&lt;p&gt;I also want to cover rendering points directly, which doesn&amp;rsquo;t sound very exciting, but can actually be very cool. So stay tuned on a few additional posts on this library.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-code&#34;&gt;The Code&lt;/h2&gt;&#xA;&lt;p&gt;The library is up on github already at &lt;a href=&#34;https://github.com/bit101/wire&#34;&gt;https://github.com/bit101/wire&lt;/a&gt;. As I said, it all depends on my other two personal libraries, so it&amp;rsquo;s unlikely you&amp;rsquo;ll wind up using it directly, but there might be some interesting techniques in there for you to peruse.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>MRI</title>
      <link>https://bit-101.com/blog/posts/2024-05-03/mri/</link>
      <pubDate>Fri, 03 May 2024 19:28:36 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-05-03/mri/</guid>
      <description>&lt;p&gt;I went in for my MRI today to see what&amp;rsquo;s happening with my &lt;a href=&#34;https://bit-101.com/blog/posts/2024-04-30/i-broke-my-rotation-matrix/&#34;&gt;rotator cuff&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I had an MRI once before, quite a few years ago. I tend to have a claustrophobic reaction to small tight places like tunnels. Even the thought of crawling through a tunnel or watching a movie where that&amp;rsquo;s happening gives me a physical reaction. It&amp;rsquo;s something to do with the thought of not being able to raise my arms or turn around or go backwards. Just writing this, I&amp;rsquo;m shuddering. Anyway, last time I had an MRI they asked if I&amp;rsquo;d have a problem. I looked at the machine and was like, &amp;ldquo;Nah, it&amp;rsquo;s so short and I can see out both ends. No problem.&amp;rdquo; Then as I was moved into the tube, I started to panic. It took a lot of self control and measured breathing, but I got through it.&lt;/p&gt;&#xA;&lt;p&gt;So in we go again today. Last time they offered a sedative. I would have taken it this time, but it wasn&amp;rsquo;t offered and I was embarrased to ask. My wife suggested shutting my eyes before I go in and keeping them shut. So I did that. They also offered headphones and asked what kind of music I wanted. I said classic rock. Yeah, a lot of it&amp;rsquo;s crap, but it&amp;rsquo;s my crap. I didn&amp;rsquo;t want to be in there listening to current music that I have zero connection or affinity with. So we got some Bad Company, Bon Jovi, Doors, Led Zeppelin, ZZ Top, and some others. Incidentally, the sounds the MRI was making during the ZZ Top tune matched the guitar track to a degree that was uncanny. Rhythm, tone, timbre. They actually started resonating with each other causing a fascinating audio effect. Although I&amp;rsquo;m not a huge ZZ Top fan, this was magical.&lt;/p&gt;&#xA;&lt;p&gt;But I digress.&lt;/p&gt;&#xA;&lt;p&gt;Shut my eyes and kept them shut, rolled in, listened to the music, rolled out twenty minutes later. No problem! I was very happy about that.&lt;/p&gt;&#xA;&lt;p&gt;This evening I got the written report. I&amp;rsquo;ve got a partial tear on one side of my supraspinatus tendon with a 9mm retraction and another tear on the other side. Two other posisble tears elsewhere.&lt;/p&gt;&#xA;&lt;p&gt;You know what that means? Me neither. At least it&amp;rsquo;s not a full width tear. But I&amp;rsquo;m not sure if these are small enough that they could potentially heal on their own, or if surgery will be necessary. I&amp;rsquo;ll talk to my doctor on Monday.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/photo-of-ct-scan-medical-equipment-7089013/&#34;&gt;Header photo by MART PRODUCTION&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>I Broke My Rotation Matrix</title>
      <link>https://bit-101.com/blog/posts/2024-04-30/i-broke-my-rotation-matrix/</link>
      <pubDate>Tue, 30 Apr 2024 21:48:26 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-04-30/i-broke-my-rotation-matrix/</guid>
      <description>&lt;p&gt;The clickbait title actually refers to my shoulder / rotator cuff. And it&amp;rsquo;s not actually broken, though it might be torn.&lt;/p&gt;&#xA;&lt;h2 id=&#34;phase-zero&#34;&gt;Phase Zero&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve had pain in my right arm down under the biceps for over a year. I thought it was a pinched nerve and hoped it would go away on its own. I did stretching and some light strength training. It didn&amp;rsquo;t seem to make anything worse, but didn&amp;rsquo;t help either. Finally, last September I talked to my doctor about it.&lt;/p&gt;&#xA;&lt;p&gt;Turns out it&amp;rsquo;s my rotator cuff. I didn&amp;rsquo;t even know what a rotator cuff was. In case you don&amp;rsquo;t, it&amp;rsquo;s the term applied to the four muscles that come in over the front, back and top of your shoulder and stabilize the top of your upper arm bone (humerus). They attach to the head of that bone via tendons. These can become inflamed, or frayed, torn, or totally detached.&lt;/p&gt;&#xA;&lt;h2 id=&#34;phase-one&#34;&gt;Phase One&lt;/h2&gt;&#xA;&lt;p&gt;First order of business was try to physical therapy. I did that from the beginning of October 2023 through mid-December. I&amp;rsquo;ve had a couple of really amazing experiences with PT in the past. This one was not great. I was actually in more pain when I finally quit. I continued doing some PT on my own until I saw my doctor again in January.&lt;/p&gt;&#xA;&lt;h2 id=&#34;phase-two&#34;&gt;Phase Two&lt;/h2&gt;&#xA;&lt;p&gt;Phase two was to go to an orthopedic specialist. I did that in early February. She was great. Answered a lot of questions and gave me a choice:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Get a cortisone shot and see how that plays out over the next couple of months. Cortisone reduces inflammation and can sometimes actually cure the condition if that&amp;rsquo;s all that&amp;rsquo;s going on.&lt;/li&gt;&#xA;&lt;li&gt;Get an MRI to see what&amp;rsquo;s going on. If there&amp;rsquo;s a tear, then we could be looking at surgery.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I opted to get the shot and see what that did for me. It helped a lot! For a couple of weeks. Then, week by week, it reverted to how it was before, and worse.&lt;/p&gt;&#xA;&lt;p&gt;Sleeping is really bad. Most nights by 3 or 4 am I am awakened with throbbing pain. Sometimes I can get in a better position and go back to sleep. Other times I give up, get out of bed and go do an early dog walk. Moving around helps and sometimes I can even get another couple hours of sleep after that.&lt;/p&gt;&#xA;&lt;p&gt;There are a lot of motions I do inadvertently with my right (dominant) arm that cause extreme pain. I can compare it to stubbing your toe really badly - where you just have to stop everything and close your eyes until it subsides. Some days I&amp;rsquo;ll just have shooting pains down from my shoulder to my elbow, twitches, and sudden weakness to the point of almost dropping things. Doing day to day activities like getting dressed, washing my hair, driving, getting things off a shelf are becoming increasingly difficult.&lt;/p&gt;&#xA;&lt;h2 id=&#34;phase-three&#34;&gt;Phase Three&lt;/h2&gt;&#xA;&lt;p&gt;The next action comes in a couple of days (Friday) when I go back and get an MRI. Given my steadily worsening symptoms, I&amp;rsquo;m self-diagnosing that I&amp;rsquo;ve probably torn something in there. But we&amp;rsquo;ll see when I talk to the doctor again on the following Monday.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been educating myself on rotator cuff surgery and while it&amp;rsquo;s a relatively quick process - generally under an hour as an outpatient procedure - the recovery can be brutal. But again, we&amp;rsquo;ll see if that&amp;rsquo;s my path or not within the next week.&lt;/p&gt;&#xA;&lt;p&gt;By the way, the header image above is actually my shoulder. When I went to see the orthopedic doctor, they did an xray to rule out any issues with the bones of the shoulder. All good there, but they can&amp;rsquo;t see the muscles and tendons with an xray.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Aliexpress Impulse Buys</title>
      <link>https://bit-101.com/blog/posts/2024-04-27/aliexpress-impulse-buys/</link>
      <pubDate>Sat, 27 Apr 2024 11:51:22 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-04-27/aliexpress-impulse-buys/</guid>
      <description>&lt;p&gt;I was perusing Aliexpress a week or two ago and bought some cheap, fun things. Three purchases, which came to a total of around $13, with free shipping. This was part of some kind of buy three things for a reduced price offer. Here is what I got:&lt;/p&gt;&#xA;&lt;img alt=&#34;misc cheap electronics&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-27/aliexpress.jpg&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;mini-weather-cube&#34;&gt;Mini Weather Cube&lt;/h3&gt;&#xA;&lt;p&gt;This is the top left. It&amp;rsquo;s delightful!&lt;/p&gt;&#xA;&lt;p&gt;USB-C powered, wifi connected. There are zero interactive controls on this thing. You plug it in and it creates a wifi network. You connect another device to that network and from there configure it to connect to your actual network. Then you hope and pray and trust in your good karma that this thing doesn&amp;rsquo;t just exist to infiltrate you.&lt;/p&gt;&#xA;&lt;p&gt;From there on, it&amp;rsquo;s just on your local network and it sets up a mini server with a page to configure all the device&amp;rsquo;s options. The config program is pretty extensive. There are a few different weather screens and a few different clocks, and an image slideshow with predefined or custom images. You can also set it to cycle through modes. It has day/night mode to dim it while you sleep.&lt;/p&gt;&#xA;&lt;p&gt;I freaking love this thing.&lt;/p&gt;&#xA;&lt;p&gt;Link: &lt;a href=&#34;https://www.aliexpress.us/item/3256806253672298.html&#34;&gt;https://www.aliexpress.us/item/3256806253672298.html&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;cheap-bluetooth-earbuds&#34;&gt;Cheap Bluetooth Earbuds&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not sure why I chose these. They&amp;rsquo;re pretty bad. To be fair, they actually sound better than I expected. But I still don&amp;rsquo;t know which is right and left because they are unmarked. The tips they came with are tiny, but I have plenty of spare earbud tips around so I was able to swap them out. They paired and connected OK, but they don&amp;rsquo;t seem to auto-connect later, so you have to manually have to connect them each time. There is a tap to play/pause, but there&amp;rsquo;s no beep or feedback. I didn&amp;rsquo;t test other double/triple tap or hold gestures. They have bright LEDs on them that flash intermittently when they&amp;rsquo;re in use, which is annoying in a dark room. But I just figured it&amp;rsquo;d be good to have some emergency spares around, and for the price, I can&amp;rsquo;t complain too much.&lt;/p&gt;&#xA;&lt;p&gt;Link: &lt;a href=&#34;https://www.aliexpress.us/item/3256806172256904.html&#34;&gt;https://www.aliexpress.us/item/3256806172256904.html&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;three-key--knob-control-pad&#34;&gt;Three-key + Knob Control Pad&lt;/h3&gt;&#xA;&lt;p&gt;This is pretty neat, but was a major pain in the butt to set up. It connects to your computer via USB-C and is theoretically configurable. We&amp;rsquo;ll get to that.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve built some DIY devices similar to this:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.bit-101.com/2017/2020/12/learning-cnc-and-making-a-mediabox/&#34;&gt;This one with six media keys&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;And another one that&amp;rsquo;s just a single volume knob.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;But this looked pretty neat and is a combo of keys plus knob.&lt;/p&gt;&#xA;&lt;p&gt;I plugged it in and it did nothing! It came with zero instructions, but the product page on Aliexpress gave a URL to get config software. That URL 403ed. I couldn&amp;rsquo;t find any other link to the software. I almost resigned myself to the fact that this was going to be a little lucite brick.&lt;/p&gt;&#xA;&lt;p&gt;But after a pretty extensive search, I found this one guy who created his own software for configuring these kinds of devices. What a hero!&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/rOzzy1987/MacroPad&#34;&gt;https://github.com/rOzzy1987/MacroPad&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s Windows only, so I booted into my Windows partition, ran the config program and set up the keys to do previous track, play/pause, and next track. The knob controls volume, and clicking on the knob mutes/unmutes. But really you can set up they controls to do just about anything you want. Once you send the config to the device, it&amp;rsquo;s all set and should work on any computer/OS. Booted back into Linux and it&amp;rsquo;s golden.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s nice since my Thinkpad does not have media keys. It has volume keys, but having the knob is really nice.&lt;/p&gt;&#xA;&lt;p&gt;Link: &lt;a href=&#34;https://www.aliexpress.us/item/3256806557559192.html&#34;&gt;https://www.aliexpress.us/item/3256806557559192.html&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;anyway&#34;&gt;Anyway&amp;hellip;&lt;/h3&gt;&#xA;&lt;p&gt;Just some fun stuff. I imagine those links won&amp;rsquo;t last forever, but there are plenty of other similar devices all over the site, and similar ones on Amazon and other places.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Another Paradox</title>
      <link>https://bit-101.com/blog/posts/2024-04-22/another-paradox/</link>
      <pubDate>Mon, 22 Apr 2024 12:49:56 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-04-22/another-paradox/</guid>
      <description>&lt;p&gt;Take the sentence:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;You have not read this sentence.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;It was true, up until the point you read it. Then it was false. Alternately&amp;hellip;&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;You have read this sentence.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This was false, until you read it. Then it was true.&lt;/p&gt;&#xA;&lt;p&gt;By reading the sentence, you changed its meaning. A Heisenberg sentence?&lt;/p&gt;&#xA;&lt;p&gt;Oddly, the first sentence is still true for those who have not read it yet. But I think &amp;ldquo;yet&amp;rdquo; is important here. What about the billions of people who will never read that sentence? The &amp;ldquo;you&amp;rdquo; in the sentence refers to the reader, so if they never read it, they are not described by &amp;ldquo;you&amp;rdquo;, so the sentence has no meaning for them. I think it is neither true nor false.&lt;/p&gt;&#xA;&lt;p&gt;Even more oddly, once someone does read it, that&amp;rsquo;s when the &amp;ldquo;you&amp;rdquo; takes on meaning. But at that point, the &amp;ldquo;you&amp;rdquo; refers to them even before they read it. So the fact of observing the sentence changes its meaning not only in the present and future but in its past as well. Weird.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Keith&#39;s Paradox</title>
      <link>https://bit-101.com/blog/posts/2024-04-22/keiths-paradox/</link>
      <pubDate>Mon, 22 Apr 2024 10:21:29 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-04-22/keiths-paradox/</guid>
      <description>&lt;h3 id=&#34;definition&#34;&gt;Definition&lt;/h3&gt;&#xA;&lt;p&gt;When I say &amp;ldquo;edit&amp;rdquo; here, I mean &amp;ldquo;to change from the original.&amp;rdquo;&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-paradox&#34;&gt;The Paradox&lt;/h3&gt;&#xA;&lt;p&gt;If I write the following sentence:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;This sentence has been edited.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;It is false. I wrote the sentence and didn&amp;rsquo;t edit it. OK, so I fix the sentence to make it true:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;This sentence has not been edited.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;But now I&amp;rsquo;ve edited it. So it&amp;rsquo;s still false!&lt;/p&gt;&#xA;&lt;p&gt;There are many of these self-referential statement paradoxes, like &amp;ldquo;This sentence is false.&amp;rdquo; I haven&amp;rsquo;t seen this one around, but wouldn&amp;rsquo;t be suprised if someone has used it before.&lt;/p&gt;&#xA;&lt;h3 id=&#34;other-questions&#34;&gt;Other Questions&lt;/h3&gt;&#xA;&lt;p&gt;If I write the first sentence and underneath it I write the second sentence, than the first one is false and in this case the second one is true because they are two different sentences.&lt;/p&gt;&#xA;&lt;p&gt;What if I write the first sentence, then delete it and write the second one? Is that a new sentence or an edited version of the first one?&lt;/p&gt;&#xA;&lt;p&gt;What if I write the first sentence and then delete that document and write the second one? Or write the first on a piece of paper, then throw that paper away and write the second sentence on another paper. That feels like a new document, not an edited one.&lt;/p&gt;&#xA;&lt;p&gt;But what if I wrote a paragraph or essay that contained the first sentence, then deleted the doc and started a new one that was word-for-word the same except it contained the second sentence instead of the first? That feels edited.&lt;/p&gt;&#xA;&lt;p&gt;Now we&amp;rsquo;re into the nature of identity. Is the same sentence written twice the same sentence? Is an edited sentence just a different version of the original, or is it a new sentence? Ship of Theseus stuff.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>MacOS Home Directory</title>
      <link>https://bit-101.com/blog/posts/2024-04-13/macos-home-directory/</link>
      <pubDate>Sat, 13 Apr 2024 11:00:17 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-04-13/macos-home-directory/</guid>
      <description>&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;&#xA;&lt;p&gt;A funny (not) thing happened the other day. My beloved Thinkpad slipped off the edge of a chair. It wasn&amp;rsquo;t a big fall and it fell to a carpeted floor so all seemed fine. But the next day I went to plug something into one of the USB-C ports and discovered it was just an empty hole. When the laptop fell, the power connector was in and it pushed the USB-C port in and seemingly broke it right off. Luckily there are two of them, so it all works fine and charges OK. But I plan on taking it in to see if I can get it repaired. I know it will be in for a few days, so I&amp;rsquo;m prepping my Mac Book Air to serve as a backup.&lt;/p&gt;&#xA;&lt;p&gt;What I use the Thinkpad for is mostly my personal coding stuff as well as browsing, media consumption, etc. My biggest concern was having a functional coding setup for my creative coding projects and any other learning / exploration I want to do. I pretty much had everything set up earlier. I use MacPorts to install all my dev stuff and it all works mostly fine. But for one thing&amp;hellip;&lt;/p&gt;&#xA;&lt;h3 id=&#34;why-do-they-do-this&#34;&gt;Why do they do this???&lt;/h3&gt;&#xA;&lt;p&gt;In all its infinite wisdom, Apple has chosen to ignore the standard Unix/Linux standard of using &lt;code&gt;/home&lt;/code&gt; as the location of user&amp;rsquo;s home directories, instead opting for &lt;code&gt;/Users&lt;/code&gt;. Think Different, I guess. In general, I&amp;rsquo;ve mostly gotten around this by using the &lt;code&gt;~&lt;/code&gt; shortcut in scripts, or &lt;code&gt;$HOME&lt;/code&gt;. This will go to &lt;code&gt;/home/keith&lt;/code&gt; on Linux and &lt;code&gt;/Users/keith&lt;/code&gt; on Mac.&lt;/p&gt;&#xA;&lt;p&gt;But there&amp;rsquo;s one place I can&amp;rsquo;t do that. In my Go projects, I set up a workspace that links to local copies of my personal libraries. For this I need to give the &lt;code&gt;go.work&lt;/code&gt; file a path to those libraries. Normally it&amp;rsquo;s like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/home/keith/Code/golang/libs/bitlib&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/home/keith/Code/golang/libs/blcairo&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Not the best setup in the world, but this lets me add new functions to my libraries on the fly as I&amp;rsquo;m developing a particular project. They&amp;rsquo;ll then be usable in other projects.&lt;/p&gt;&#xA;&lt;p&gt;Sadly, you cannot use &lt;code&gt;~&lt;/code&gt; or &lt;code&gt;$HOME&lt;/code&gt; here. So every project I want to build on my Mac, I&amp;rsquo;d have to go in and change the path to &lt;code&gt;/Users/...&lt;/code&gt; and then when I get my Thinkpad back, I&amp;rsquo;d have to go change them back. No thanks.&lt;/p&gt;&#xA;&lt;h3 id=&#34;warning&#34;&gt;Warning&lt;/h3&gt;&#xA;&lt;p&gt;Some of the stuff I&amp;rsquo;m going to describe in the next two sections is potentially dangerous to your Mac&amp;rsquo;s configuration. That said, I&amp;rsquo;m not deleting anything, just commenting out one line in a file and adding one line in another file. Easily revertible. But do your own research and make up your own mind as to the safety of this.&lt;/p&gt;&#xA;&lt;h3 id=&#34;easy-fix&#34;&gt;Easy fix?&lt;/h3&gt;&#xA;&lt;p&gt;My brilliant mind said, &amp;ldquo;Just create a &lt;code&gt;/home&lt;/code&gt; symlink that points to &lt;code&gt;/Users&lt;/code&gt;.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;In the end, that&amp;rsquo;s what I did, and it works great! But there were a couple of hoops to jump through first.&lt;/p&gt;&#xA;&lt;p&gt;First, oddly enough &lt;code&gt;/home&lt;/code&gt; DOES exist on a Mac. It may be hidden. But it&amp;rsquo;s there and you can&amp;rsquo;t replace it. It points to &lt;code&gt;/System/Volumes/Data/home&lt;/code&gt;. It&amp;rsquo;s probably empty. But if you try to delete it or unlink it, it won&amp;rsquo;t let you.&lt;/p&gt;&#xA;&lt;p&gt;Apparently, this is part of a system designed to auto-mount network volumes. The line that sets it up is in &lt;code&gt;/etc/auto_master&lt;/code&gt;. It&amp;rsquo;s this one:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/home&#x9;&#x9;&#x9;auto_home&#x9;-nobrowse,hidefromfinder&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When your Mac boots, it will auto-mount &lt;code&gt;/System/Volumes/Data/home&lt;/code&gt; to &lt;code&gt;/home&lt;/code&gt;. You can disable this by commenting out this line (you&amp;rsquo;ll have to do this with &lt;code&gt;sudo&lt;/code&gt; of course):&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;#/home&#x9;&#x9;&#x9;auto_home&#x9;-nobrowse,hidefromfinder&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To be perfectly honest with you, I don&amp;rsquo;t know for certain what side effects this might have. But from everything I&amp;rsquo;ve read, if you&amp;rsquo;re not mounting network volumes, you should be OK. I&amp;rsquo;m not and so far, so good. Worst case, uncomment the line and you&amp;rsquo;re back to normal.&lt;/p&gt;&#xA;&lt;p&gt;Reboot your Mac and that &lt;code&gt;/home&lt;/code&gt; symlink will be gone.&lt;/p&gt;&#xA;&lt;h3 id=&#34;ok-now-its-an-easy-fix-right--right&#34;&gt;OK, now it&amp;rsquo;s an easy fix, right? &amp;hellip; right???&lt;/h3&gt;&#xA;&lt;p&gt;Yeah, so now we just do &lt;code&gt;ln -s /Users /home&lt;/code&gt; right? Simple.&lt;/p&gt;&#xA;&lt;p&gt;Nah. It&amp;rsquo;s gonna tell you you can&amp;rsquo;t do this on a read-only file system. OK. Workaround, please?&lt;/p&gt;&#xA;&lt;p&gt;Yes, one workaround coming right up!&lt;/p&gt;&#xA;&lt;p&gt;You create root symlinks using a special file, &lt;code&gt;/etc/synthetic.conf&lt;/code&gt;. Of course! Why not? That file probably won&amp;rsquo;t exist, unless you&amp;rsquo;ve done this before, so just create it (using &lt;code&gt;sudo&lt;/code&gt;) and add this line:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;home&#x9;Users&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Super important guidelines for this:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;No leading &lt;code&gt;/&lt;/code&gt; character. It&amp;rsquo;s assumed.&lt;/li&gt;&#xA;&lt;li&gt;The whitespace in between &lt;code&gt;home&lt;/code&gt; and &lt;code&gt;Users&lt;/code&gt; has to be a single TAB character. Not a space or multiple spaces or any other whitespace characters or anything else.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;If you mess this up, you may wind up in a boot loop, at which point you&amp;rsquo;ll have to do some kind of recovery mode nonsense. So look this up and have the instructions on hand in case you need to do that. I was lucky enough to not mess it up the first time.&lt;/p&gt;&#xA;&lt;h3 id=&#34;success&#34;&gt;Success&lt;/h3&gt;&#xA;&lt;p&gt;Everything works fine! All my projects that link to &lt;code&gt;/home/keith/...&lt;/code&gt; now work on the Mac. And these projects will continue to work on Linux as well, with no changes needed for either system. I&amp;rsquo;m happy.&lt;/p&gt;&#xA;&lt;p&gt;But again, do this at your own risk and have a revert and recovery plan before you start messing with this stuff. I&amp;rsquo;m using this for a very niche use case. There may be a better solution for this, but I couldn&amp;rsquo;t figure one out. In 99.99% of other cases, I&amp;rsquo;d recommend using &lt;code&gt;$HOME&lt;/code&gt; or &lt;code&gt;~&lt;/code&gt; to point to your home directory.&lt;/p&gt;&#xA;&lt;p&gt;That all said, it&amp;rsquo;s pretty cool to be able to do this!&lt;/p&gt;&#xA;&lt;h3 id=&#34;update&#34;&gt;UPDATE!&lt;/h3&gt;&#xA;&lt;p&gt;While the above worked great, it still felt pretty hacky. After much thought, I came up with another way.&lt;/p&gt;&#xA;&lt;p&gt;Rather than relying on the actual path to my libraries, I created a symlink in the root directory that pointed to the library folder.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# On Mac via synthetic.conf:&#xA;/bitlib -&amp;gt; /Users/keith/Code/golang/libs&#xA;&#xA;# On Linux via ln -s:&#xA;/bitlib -&amp;gt; /home/keith/Code/golang/libs&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now in my &lt;code&gt;go.work&lt;/code&gt; file I can point to:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bitlib&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bitlib&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;bitlib&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;blcairo&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is way more portable and only requires a one-time set up on any given machine. I reverted the changes I made above and I&amp;rsquo;m going with this solution for now. I&amp;rsquo;ll leave the post up in case it helps someone with some future problem though.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Subpixel Sampling</title>
      <link>https://bit-101.com/blog/posts/2024-04-09/subpixel-sampling/</link>
      <pubDate>Tue, 09 Apr 2024 16:18:52 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-04-09/subpixel-sampling/</guid>
      <description>&lt;h3 id=&#34;the-set-up&#34;&gt;The Set Up&lt;/h3&gt;&#xA;&lt;p&gt;In my last post on &lt;a href=&#34;https://bit-101.com/blog/posts/2024-04-07/graphing-equations/&#34;&gt;Graphing Equations&lt;/a&gt; I showed how I graph an equation by visiting every pixel in the canvas, seeing if it satisfies that equation (or comes close to solving it) and then coloring that pixel appropriately. In that case, I was just coloring each pixel either black or white. In general it looked great, but you may have noticed some aliasing going on. The dreaded stair steps. Today we&amp;rsquo;ll look at how subpixel sampling can help solve that.&lt;/p&gt;&#xA;&lt;h3 id=&#34;recreating-the-problem&#34;&gt;Recreating the problem.&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;m going to start with some code and the image it creates. It&amp;rsquo;s a similar kind of thing to what I created in the earlier post. I just experimented with &lt;code&gt;cosh&lt;/code&gt; in place of some of the &lt;code&gt;cos&lt;/code&gt; instances and tweaked some other values. If you read the previous post, you should be able to work this out.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; y &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; height; y&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    xx &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(x, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, width, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    yy &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(y, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, height, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    a &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sin(sin(xx) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cosh(yy))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    b &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; cos(sin(xx &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; yy) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cosh(xx))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    d &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; abs(a &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; b)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (d &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.125&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      fillRect(x, y, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Anyway, to recap, we&amp;rsquo;re hitting each pixel, mapping it to a -14 to 14 range, running two expressions and seeing if the results of those two are equal (or close to being equal - less than 0.125 close). If so, we fill that pixel with the default color, which will be black. Otherwise, it remains white.&lt;/p&gt;&#xA;&lt;p&gt;This is what we get:&lt;/p&gt;&#xA;&lt;img alt=&#34;aliased!&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;It looks &amp;hellip; OK. But let&amp;rsquo;s zoom in a bit&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;aliased! closeup&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Yeah, not very OK. The problem is we&amp;rsquo;re getting the value of each pixel and setting it to black or white. So it&amp;rsquo;s all very hard, rectangular images that don&amp;rsquo;t look great.&lt;/p&gt;&#xA;&lt;p&gt;We could apply a blur&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;gaussian blur&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;That smooths it out a bit, but blurs everything and doesn&amp;rsquo;t really solve the problem. What we want is to have more shades of gray around the edges. One way to do this would be to render it at a higher resolution and then scale it down. The scaling algorithm then blurs those pixels on the edge just right usually. I rendered this at 3200x3200 and then scaled it back down to 800x800, and it looks pretty good:&lt;/p&gt;&#xA;&lt;img alt=&#34;rendered large, scaled down&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;But then we&amp;rsquo;re rendering 10,240,000 pixels (3200x3200) to get 640,000 (800x800) and having to open up an image processing program or call out to ImageMagick or something. It would be nice to be able to figure this out in code alone.&lt;/p&gt;&#xA;&lt;h3 id=&#34;a-solution-subpixel-sampling&#34;&gt;A Solution: Subpixel Sampling&lt;/h3&gt;&#xA;&lt;p&gt;The basic concept of subpixel sampling is that for every pixel you are rendering, you sample it multiple times, as a small offset, but still within the bounds of that pixel. Then you take the average of all these samples and use that as the color for that pixel. It&amp;rsquo;s kind of the same as rendering the image larger and scaling it down, but you&amp;rsquo;re doing it all in one go, for each pixel.&lt;/p&gt;&#xA;&lt;p&gt;There are a few different strategies for this.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;One is to randomly sample various points within the pixel.&lt;/li&gt;&#xA;&lt;li&gt;Another is to sample a grid of subpixel locations.&lt;/li&gt;&#xA;&lt;li&gt;The most complicated is to recursively sample smaller and smaller grids until the values converge for each section, then merge them back up to the top to get the final pixel value.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The final strategy is sometimes used in ray tracing, but it would be overkill here. We&amp;rsquo;ll examine the first two, though.&lt;/p&gt;&#xA;&lt;h4 id=&#34;random-sampling&#34;&gt;Random Sampling&lt;/h4&gt;&#xA;&lt;p&gt;This is pretty simple. We choose a number of samples and randomly generate a bunch of floating point pixel values between the current and next pixel. For example, if we&amp;rsquo;re looking at pixel 300, 400, we&amp;rsquo;d get say 20 different values for x, from 300.0 to 301.0. And from 400.0 to 401.0 on y.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s how that might look:&lt;/p&gt;&#xA;&lt;img alt=&#34;random sampling&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The pixel we&amp;rsquo;re sampling lies on the edge of a curved shape. Without antialiasing, we&amp;rsquo;d just sample at exactly 300, 400, and color that whole pixel white. Here, we&amp;rsquo;ve sampled twenty subpixel locations. About half of them are on the shape and half are off, so this pixel value would get somewhere around mid level gray rather than just plain black or white. The pixel one down and to the left only overlaps the shape a little bit, so would likely get fewer hits of black and would be a lighter shade of gray. The top left pixel would be all white and any of the pixels entirely in the shape would be entirely black.&lt;/p&gt;&#xA;&lt;p&gt;So let&amp;rsquo;s code that.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; y &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; height; y&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (s &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; s &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;; s&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      x1 &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; x &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; randomFloat()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      y1 &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; randomFloat()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      xx &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(x1, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, width, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      yy &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(y1, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, height, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      a &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sin(sin(xx) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cosh(yy))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      b &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; cos(sin(xx &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; yy) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cosh(xx))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      d &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; abs(a &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; b)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (d &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.125&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// theoretical color function. 0 is black, 1 is white.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setGray(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; value&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fillRect(x, y, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We start with a &lt;code&gt;value&lt;/code&gt; for the pixel set to 0. Then we loop through 20 times.&lt;/p&gt;&#xA;&lt;p&gt;Each iteration, we add a random floating point value from 0.0 to 1.0 to &lt;code&gt;x&lt;/code&gt;, storing it in &lt;code&gt;x1&lt;/code&gt;. And the same for &lt;code&gt;y&lt;/code&gt; to &lt;code&gt;y1&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Then we do our previous calculation with &lt;code&gt;x1&lt;/code&gt;, &lt;code&gt;y1&lt;/code&gt;. But if it&amp;rsquo;s within range, rather than coloring the pixel then and there, we just add 1 to &lt;code&gt;value&lt;/code&gt;. When we&amp;rsquo;re done with our sampling, we divide by the number of samples (20) and use that as the pixel value. I have a theoretical function &lt;code&gt;setGray&lt;/code&gt; that will set the drawing color to black if it gets 0 and white if it gets 1. Because we started at a &lt;code&gt;value&lt;/code&gt; of 0 and added up, we&amp;rsquo;ll need to reverse that by subtracting &lt;code&gt;value&lt;/code&gt; from 1. Then we fill that pixel. Here&amp;rsquo;s the result:&lt;/p&gt;&#xA;&lt;img alt=&#34;random sampling&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;That looks pretty good. But not quite as good as the scaled up and scaled down version I think. And how many samples did we take? 800 x 800 x 20 = 12,800,000.&lt;/p&gt;&#xA;&lt;p&gt;And look what happens when we reduce the number of samples to 10 and zoom in:&lt;/p&gt;&#xA;&lt;img alt=&#34;random sampling&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel06a.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Because of the small sample size, sometimes the samples clump in a white section of the pixel and sometimes in the black section. So you can get ragged edges. For random sampling to work well you really need to increase the sample size. I think it also may work better on more colorful images. We&amp;rsquo;re really challenging ourselves here with black and white.&lt;/p&gt;&#xA;&lt;p&gt;But let&amp;rsquo;s change directions now and try method two, a subpixel grid.&lt;/p&gt;&#xA;&lt;h4 id=&#34;subpixel-grid&#34;&gt;Subpixel Grid&lt;/h4&gt;&#xA;&lt;p&gt;The idea here is rather than a random sampling, we purposely spread the sampled subpixels evenly across the pixel. Here&amp;rsquo;s a few examples.&lt;/p&gt;&#xA;&lt;img alt=&#34;subpixel grids&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Each square represents a single pixel. In the first one, we sample nine subpixels, sixteen in the second one and twenty-five in the third one. Of course, the more subpixels you sample, the slower your rendering is going to be. You&amp;rsquo;ll have to figure out the balance of speed and quality that works well for your purposes.&lt;/p&gt;&#xA;&lt;p&gt;The code for this is fairly easy, not too much different from the random version. We just use a couple of for loops.&lt;/p&gt;&#xA;&lt;p&gt;Le code:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sampleSize &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;spacing &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; (sampleSize &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; y &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; height; y&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; sampleSize; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (j &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; j &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; sampleSize; j&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        x1 &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; x &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; spacing&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        y1 &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; j &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; spacing&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        xx &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(x1, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, width, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        yy &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(y1, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, height, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        a &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sin(sin(xx) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cosh(yy))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        b &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; cos(sin(xx &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; yy) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cosh(xx))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        d &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; abs(a &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; b)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (d &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.125&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          value &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// theoretical color function. 0 is black, 1 is white.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setGray(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; value&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;(sampleSize &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; sampleSize)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fillRect(x, y, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We create a &lt;code&gt;sampleSize&lt;/code&gt; variable that holds the number of samples we&amp;rsquo;ll take on each axis. The total number of samples will be the square of this number.&lt;/p&gt;&#xA;&lt;p&gt;The spacing between each sample will be 1 (pixel) divided by one less than the sample size. So for a sample size of 3 the spacing would be 0.5, which fits in with the first square in the image above.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;i&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt; for loops get each sample. It&amp;rsquo;s just recreating what you see in the grids above. And the rest you&amp;rsquo;ve already seen. In the end we divide &lt;code&gt;value&lt;/code&gt; by the square of &lt;code&gt;sampleSize&lt;/code&gt;. So for a sample size of 3, we divide by 9.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s how it looks with a sample size of 3. That&amp;rsquo;s 9 samples per pixel.&lt;/p&gt;&#xA;&lt;img alt=&#34;sample size 3&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel08.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And a sample size of 4, 16 samples per pixel.&lt;/p&gt;&#xA;&lt;img alt=&#34;sample size 4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel09.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;This last one winds up samping the same number of pixels as the 4x image scaled down: 10,240,000. If I&amp;rsquo;m honest, the manually image-processed one looks a little better. But I&amp;rsquo;m pretty happy with this. If I&amp;rsquo;m generating a ton of frames for an animation, I&amp;rsquo;m not going to manually process each one. And in that case, the sample size 3 one would probably be just fine.&lt;/p&gt;&#xA;&lt;p&gt;I find that increasing the sample size beyond 4 has very diminished returns.&lt;/p&gt;&#xA;&lt;p&gt;Just to see how far we&amp;rsquo;ve come, here&amp;rsquo;s a combo image. On the left, subpixel sampling with a sample size of 4. On the right, there&amp;rsquo;s no antialiasing at all.&lt;/p&gt;&#xA;&lt;img alt=&#34;sample size 4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel10.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;m no expert in this field, but this stuff was pretty easy to work out and gives decent results. I&amp;rsquo;m not sure if sampling edge-to-edge like I&amp;rsquo;m doing gives the best results. Maybe instead of going to both edges like in the first square below&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;sample size 4&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-09/subpixel11.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;&amp;hellip;it might be better to avoid one edge like in the second square. Or avoid all the edges and sample completely inside the pixel like in the third square. I&amp;rsquo;m sure smarter people than me have worked on this problem and have come up with better answers than me. But now you have something to look into maybe.&lt;/p&gt;&#xA;&lt;p&gt;You&amp;rsquo;ll probably find the most resources on this stuff in ray tracing books and articles. That&amp;rsquo;s where I first ran across practical applications of the concept &lt;a href=&#34;https://www.bit-101.com/2017/2022/10/my-raytracing-journey/&#34;&gt;when I built my own ray tracer a year and a half ago&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Graphing Equations</title>
      <link>https://bit-101.com/blog/posts/2024-04-07/graphing-equations/</link>
      <pubDate>Sun, 07 Apr 2024 12:26:57 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-04-07/graphing-equations/</guid>
      <description>&lt;h3 id=&#34;set-up&#34;&gt;Set up&lt;/h3&gt;&#xA;&lt;p&gt;This post is about programming strategies for graphing various equations and functions. I often see some interesting graph and an accompanying equation or functions and I want to recreate it and play with it a bit and see what else I can do with it. The thing is, there are a few different ways these things can be presented. So I&amp;rsquo;ll go through some of these and we can explore some strategies.&lt;/p&gt;&#xA;&lt;h3 id=&#34;functions&#34;&gt;Functions&lt;/h3&gt;&#xA;&lt;p&gt;Functions are pretty easy in general. If you think back to your school days, a mathematical function looks something like this: &lt;code&gt;y = f(x)&lt;/code&gt;, where &lt;code&gt;f(x) = x + 3&lt;/code&gt; for example. Pretty similar to programming functions and you can usually map between the two pretty easily. An important thing to remember about functions is that for any given &lt;code&gt;x&lt;/code&gt;, there will always be exactly one &lt;code&gt;y&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;So say we had &lt;code&gt;f(x) = sin x&lt;/code&gt;. Easy. We create a function in code like so (in somewhat JavaScript-y pseudocode):&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; firstFunc(x) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; sin(x)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can run through a bunch of &lt;code&gt;x&lt;/code&gt; values, say from 0 to the width of our canvas, and get &lt;code&gt;y&lt;/code&gt; values for each of them and draw segments. Maybe something like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; firstFunc(x)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  lineTo(x, y)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;sine function&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/func01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now you just see some blips on the top edge of the canvas. So we need to do a few things to adjust the range of the output graph. Move it down a bit, increase the amplitude and wavelength. You can do this by translating and scaling but that can have resolution side effects, so we&amp;rsquo;ll do it with math.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; firstFunc(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.1&lt;/span&gt;) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;40&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  lineTo(x, y)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I multiply &lt;code&gt;x&lt;/code&gt; by 0.1 to increase the wavelength. Multiply the result of &lt;code&gt;firstFunc&lt;/code&gt; by 40 to increase the amplitude, and add 50 to that to move it down to the center of the image.&lt;/p&gt;&#xA;&lt;img alt=&#34;sine function&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/func02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Notice that for each point on the x-axis, there is exactly one point it matches on the y-axis. So functions are pretty easy. But what if you have something like a circle?&lt;/p&gt;&#xA;&lt;img alt=&#34;circle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/circle.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;For some &lt;code&gt;x&lt;/code&gt; values there is no corresponding &lt;code&gt;y&lt;/code&gt; value. And for some there are two &lt;code&gt;y&lt;/code&gt; values. So this is not a function and we need to do something else.&lt;/p&gt;&#xA;&lt;h3 id=&#34;parametric-equations&#34;&gt;Parametric Equations&lt;/h3&gt;&#xA;&lt;p&gt;Parametric equations take a parameter (or multiple parameters). For two-dimensional graphs, there&amp;rsquo;s often two functions based on the same parameter. One function returns the &lt;code&gt;x&lt;/code&gt; coordinate of a point, and the other returns the &lt;code&gt;y&lt;/code&gt; coordinate. In this case, I&amp;rsquo;m using &amp;ldquo;function&amp;rdquo; in the less strict, or programming sense.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s common in trigonometric parametric equations to have the parameter be called &amp;ldquo;theta&amp;rdquo; and is often defined by the letter &lt;code&gt;t&lt;/code&gt;. In these cases, theta is an angle that can range from 0 to 2 PI. The parametric equation(s) for a circle might then look like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; circle(t) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; cos(t)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sin(t)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; x, y&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My theoretical pseudocode language allows returning two values from a function. If your real world language doesn&amp;rsquo;t, you might have to return some kind of point object.&lt;/p&gt;&#xA;&lt;p&gt;Now you can draw a circle by passing in a series of &lt;code&gt;t&lt;/code&gt; values from 0 to 2 PI&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; t &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; PI; t &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.01&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  x, y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; circle(t)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  lineTo(x &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; radius, y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; radius)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since this circle function will just return values from -1 to 1, I multiplied them by a radius value. You could also offset the circle on the canvas as well. You might want to make radius and center point additional parameters to the function.&lt;/p&gt;&#xA;&lt;p&gt;Note that the amount you increase &lt;code&gt;t&lt;/code&gt; by (0.01 in this case) will affect the resolution of the circle. Increase it by too much on each iteration and you&amp;rsquo;ll get something more like a polygon. Increase it by too little and you&amp;rsquo;ll get a super smooth circle, but you might be doing way more work than you need. The trick is finding the balance.&lt;/p&gt;&#xA;&lt;p&gt;For a more interesting parametric equation, we can turn to the rose curve.&lt;/p&gt;&#xA;&lt;p&gt;The equation for a rose curve is &lt;code&gt;r = a * cos(n * t)&lt;/code&gt;. This equation takes three parameters:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;a&lt;/code&gt; is the amplitude - kind of the max radius of the rose.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;n&lt;/code&gt; is the number of nodes in the curve.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;t&lt;/code&gt; as usual is theta.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The result of this equation is not an x, y point just yet, but a radius corresponding to a particular theta angle. We can turn that into a function that returns x, y by using cosine and sine on &lt;code&gt;t&lt;/code&gt;, multiplying the result by &lt;code&gt;r&lt;/code&gt;. It will look like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;funcion rose(a, n, t) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  r &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; a &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; cos(n &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; t)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; cos(t) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; r, sin(t) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; r&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And we can use it like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; t &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; PI; t &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.01&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  x, y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; rose(&lt;span style=&#34;color:#f60&#34;&gt;150&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;, t)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  lineTo(&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; x, &lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; y)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this case, since the returned point has a useful radius, I just translate it by 200 on both axes to center the shape. Here&amp;rsquo;s the result:&lt;/p&gt;&#xA;&lt;img alt=&#34;5 petal rose&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/rose01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And if I increase &lt;code&gt;n&lt;/code&gt; to 17, we get that many nodes:&lt;/p&gt;&#xA;&lt;img alt=&#34;17 petal rose&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/rose02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;This gets even more interesting if you make &lt;code&gt;n&lt;/code&gt; a rational fraction, like 11/13. In this case however, it will take more cycles to get all the way back to the starting point. &lt;code&gt;13 * PI&lt;/code&gt; does the trick.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; t &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;13&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; PI; t &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.01&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  x, y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; rose(&lt;span style=&#34;color:#f60&#34;&gt;150&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;11.0&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;13.0&lt;/span&gt;, t)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  lineTo(&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; x, &lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; y)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;complex rose&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/rose03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;There are a lot of parametric equations that return a radius. Now you know how to handle them.&lt;/p&gt;&#xA;&lt;h3 id=&#34;more-complicated-equations&#34;&gt;More Complicated Equations&lt;/h3&gt;&#xA;&lt;p&gt;Often you&amp;rsquo;ll come across an equation that isn&amp;rsquo;t in parametric form. For example, the standard form of the circle equation is:&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;(x - h)^2 + (y -k)^2 = r^2&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;Or, in code&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;pow(x - h, 2) + pow(y - k, 2) == pow(r, 2)&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;Any set of x, y, h, k values that satisfies that equality is part of the circle. And we can rewrite this by taking the square root of each side, like so:&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;sqrt(pow(x - h, 2) + pow(y - k, 2)) == r&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;Now normally, for a circle, we wouldn&amp;rsquo;t even bother with this form, because we already know the parametric form. But let&amp;rsquo;s give it a shot as a simple use case for how to deal with things that are represented this way.&lt;/p&gt;&#xA;&lt;p&gt;One strategy is to look at every point on the canvas and see if it satisfies that equation. So we loop through &lt;code&gt;x&lt;/code&gt; from 0 to the canvas width and &lt;code&gt;y&lt;/code&gt; from 0 to its height and plug those numbers into the formula. If the conditional is true, we color that pixel. Something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;h &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;k &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;150&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;; y &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; height; y&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; sqrt(pow(x &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; h, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; pow(y &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; k, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)) &lt;span style=&#34;color:#555&#34;&gt;==&lt;/span&gt; r {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      fillRect(x, y, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, &lt;code&gt;h&lt;/code&gt; and &lt;code&gt;k&lt;/code&gt; define the center of the circle and &lt;code&gt;r&lt;/code&gt; is its radius.&lt;/p&gt;&#xA;&lt;p&gt;And that gives us&amp;hellip;&lt;/p&gt;&#xA;&lt;img alt=&#34;sparse circle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/circle01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;&amp;hellip;almost nothing. You can see a few points on the edge of the circle, but not many. The reason for this is that we&amp;rsquo;re being way to strict. We&amp;rsquo;re rarely going to find an x, y point that is exactly on the circle. if it&amp;rsquo;s even 0.00000001 of a pixel off, that&amp;rsquo;s a false. So we need to build in wiggle room. If it&amp;rsquo;s close enough to equal that&amp;rsquo;s ok.&lt;/p&gt;&#xA;&lt;p&gt;So we say that square root value is the distance from the center of the circle. If that&amp;rsquo;s exactly equal to the radius, then the difference is 0 and we want to plot that point. To give us that wiggle room, we can say if the difference between the distance and the radius is less than a small amount, that&amp;rsquo;s close enough. We&amp;rsquo;ll subtract the two and we&amp;rsquo;ll take the absolute value to get the actual un-signed distance.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;h &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;k &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;150&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;; y &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; height; y&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dist &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sqrt(pow(x &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; h, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; pow(y &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; k, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; abs(dist &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; r) &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      fillRect(x, y, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here I checked to see if that difference is less than 0.5, which gives us this:&lt;/p&gt;&#xA;&lt;img alt=&#34;sparse circle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/circle02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Of course if we increase that value, we can get a circle with a much wider boundary. Here I&amp;rsquo;m comparing to see if the distance is less than 20.&lt;/p&gt;&#xA;&lt;img alt=&#34;sparse circle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/circle03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;OK, now I said normally we wouldn&amp;rsquo;t use this method for something as simple as a circle where we already have a better method. But what if you have an equation like this?&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;sin(sin(x) + cos(y)) = cos(sin(x * y) + cos(x))&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not sure there even is a parametric form of something like this, or how I&amp;rsquo;d go about finding it. But we can pretty easily plot this the same way we just did for the circle.&lt;/p&gt;&#xA;&lt;p&gt;There will be a few steps to go through though. First we want to map our x and y canvas pixel values to a much smaller range. -14 to 14 will work well. I&amp;rsquo;m going to use the &lt;code&gt;map&lt;/code&gt; function I described in this post:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;http://bit-101.com/blog/posts/2024-01-26/fotd-map/&#34;&gt;FOTD: Map&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll assign the mapped x, y values to &lt;code&gt;xx&lt;/code&gt; and &lt;code&gt;yy&lt;/code&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;; y &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; height; y&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    xx &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(x, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, width, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;14&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    yy &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(y, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, height, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;14&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I&amp;rsquo;ll put those values into both sides of that equation. I&amp;rsquo;ll do this separately, calling the first one &lt;code&gt;a&lt;/code&gt; and the second one &lt;code&gt;b&lt;/code&gt;. Then I&amp;rsquo;ll see if they match to within a certain distance, and if so, plot that point.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;; y &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; height; y&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    xx &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(x, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, width, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;14&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    yy &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(y, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, height, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;14&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    a &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sin(sin(xx) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cos(yy))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    b &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; cos(sin(xx &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; yy) &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; cos(xx))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; abs(a&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;b) &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.25&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      fillRect(x, y, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;complex plot&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-04-07/complex.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You can try setting different values in the &lt;code&gt;map&lt;/code&gt; function to zoom in and out, and different values to compare the distance to. Here I used 0.25. Smaller values will make thinner lines and shapes. Higher values will give a more blobby look.&lt;/p&gt;&#xA;&lt;p&gt;Then start experimenting with the equation itself. And find other equations to graph. And animate them. And&amp;hellip;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>March Update</title>
      <link>https://bit-101.com/blog/posts/2024-03-10/march-update/</link>
      <pubDate>Sun, 10 Mar 2024 14:29:03 -0400</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-03-10/march-update/</guid>
      <description>&lt;p&gt;Oof! It&amp;rsquo;s been a while. Why the silence?&lt;/p&gt;&#xA;&lt;h3 id=&#34;portland&#34;&gt;Portland&lt;/h3&gt;&#xA;&lt;p&gt;A few days after my last post, I headed out to Portland, OR for several days. A couple of my older kids have wound up migrating out that way and not too long ago, my daughter had a baby boy, making me a grandfather! I&amp;rsquo;d love to show you pictures of him. He&amp;rsquo;s a very cute, very smiley kid. But I&amp;rsquo;ll let him discover the consequences of social media when he&amp;rsquo;s old enough to do that on his own. I had a very good time out there, getting to know the city (I&amp;rsquo;ve only passed through one time before) and catching up with family. It was also just great to get a good chunk of days off work, which has been pretty stressful lately.&lt;/p&gt;&#xA;&lt;h3 id=&#34;sick&#34;&gt;Sick&lt;/h3&gt;&#xA;&lt;p&gt;I caught a red-eye on the way back, took the next day off and headed into the office on the following day. In the afternoon, my stomach started feeling not-great. When I got home, I was pretty damn nauseous. By early evening, I was sicker than I&amp;rsquo;ve been in many years. Feverish and delirious, purging everything in my system in every conceivable way. I could not even tolerate a single sip of water and wound up super dehydrated. Late in the evening, my wife dragged me to the ER where they put me on an IV to get fluids into me. Spent the night there. They did a CT scan but all was fine. In the end it was just a norovirus. &amp;ldquo;Just&amp;rdquo;, sure. The next few days were better but it took almost a week before I could eat three real meals. Mainly just quarantined up in my room. But I&amp;rsquo;m back to normal now.&lt;/p&gt;&#xA;&lt;h3 id=&#34;graphs&#34;&gt;Graphs&lt;/h3&gt;&#xA;&lt;p&gt;During my trip and since then, I was pretty obsessed with graph theory. I&amp;rsquo;ve been digging through books and other resources on graphs, algorithms, data structures. It&amp;rsquo;s been really good. I&amp;rsquo;ve been implementing some graph data structures and learning a lot. So many cool algorithms that you can do with graphs. I&amp;rsquo;ll be doing more in the coming weeks.&lt;/p&gt;&#xA;&lt;h3 id=&#34;hashes&#34;&gt;Hashes&lt;/h3&gt;&#xA;&lt;p&gt;Not entirely related, but while reading up on algorithms and data structures, I ran across something that described how hashmaps/hashtables/dictionaries/etc. are implemented. This blew my mind in a few ways.&lt;/p&gt;&#xA;&lt;p&gt;For one, I always had in the back of my mind that hashmaps and hashtables and dictionaries and other similar data structures were very different things. But for the most part they are the same thing. Some languages give them slightly different different features, like one will be read-only while one is dynamic, one might only take strings as keys while another one can take other values, etc. But as far as I can tell, in general, across languages, they all refer to the same basic concept: you store a value using a key.&lt;/p&gt;&#xA;&lt;p&gt;But as for how they were implemented, I was like, &amp;ldquo;What? Implemented? They just exist, like native arrays exist as sequences of memory addresses.&amp;rdquo; I never really thought &lt;em&gt;how&lt;/em&gt; they might exist, or why they had &amp;ldquo;hash&amp;rdquo; in their name. The material I was reading just gave a general description, but it amazed me. They are not native data structures at all. They&amp;rsquo;re pretty complex implementations that sit on top of arrays.&lt;/p&gt;&#xA;&lt;p&gt;You might know more about this than me, but for the uninitiated&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;First you create an array with a given number of elements. Let&amp;rsquo;s say 100 for sake of argument. When you add something to the hashmap, you give it a key and a value. You run that key through some kind of hash function that&amp;rsquo;s going to return an integer. You can then mod that integer by the length of the array (100 in our case) so you have a number from 0 to 99. And you put the item in that element in the array. Now you&amp;rsquo;re saying, &amp;ldquo;but I&amp;rsquo;ll wind up with the same index now and then, won&amp;rsquo;t I?&amp;rdquo; Yes! You absolutely will! That&amp;rsquo;s known as a collision. So rather than just having an array of individual items, it has to be an array of collections - an array of arrays, or maybe an array of linked lists. You hash the key to get the index of the main array, then add it to the collection in that array element, along with the key itself. Or, if another element with that key is already in that sub-collection, you update its value. To get that value back, you hash the key, find the index, and iterate through the sub-collection in that element to find the actual key you&amp;rsquo;re looking for.&lt;/p&gt;&#xA;&lt;p&gt;Iterating through a long list of sub-elements takes you from the ideal of O(1) - where every item has its own element - towards O(n) - where the time is relative to the length of the sub-collections. So when your array starts to get full, you want to resize it and redistribute the elements to make those sub-collections smaller.&lt;/p&gt;&#xA;&lt;p&gt;Now there are lots of variables here. What&amp;rsquo;s the best hash function? How many elements to start with? What data structures should you use for the sub-lists? When should you resize the array? &lt;em&gt;How&lt;/em&gt; should you resize the array, and how big?&lt;/p&gt;&#xA;&lt;p&gt;Of course, every language has its own implementation of this kind of data structure, but I just had to create my own implementation in Go. It was pretty straightforward and a lot of fun to do. I created my own hash function and used linked lists as sub-collections. I ignored resizing because this is something I&amp;rsquo;ll never actually use anyway in real life. But it was a fantastic exercise and maybe I will be able to use the concepts for something or other on down the line.&lt;/p&gt;&#xA;&lt;h3 id=&#34;other-algorithm-stuff&#34;&gt;Other algorithm stuff&lt;/h3&gt;&#xA;&lt;p&gt;I also made a Go implementation of binary search and quick sort. These are things I was already familiar with, but they&amp;rsquo;re things you rarely implement in real life, so it was fun to do them again. It wound up being a good exercise to get a better handle on Go generics, which are relatively new and something I haven&amp;rsquo;t done a lot with. I wound up making a generic queue, set and stack and adding them to my toolkit for re-use in future projects. Also working on generic linked list and graph implementations in Go.&lt;/p&gt;&#xA;&lt;h3 id=&#34;wrap-it-up&#34;&gt;Wrap it up&lt;/h3&gt;&#xA;&lt;p&gt;OK, that&amp;rsquo;s what&amp;rsquo;s up with me. I&amp;rsquo;ll post another &amp;ldquo;Read/Watched&amp;rdquo; post soon with a lot of the stuff I&amp;rsquo;ve been reading recently. And I&amp;rsquo;ve been noodling on some other things as a pure distraction that I want to post about as well. See you soon.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;&#34;&gt;Header image: Mount Hood from the Pittock Mansion land in Portland, OR, by me.&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Read/Watched: 2/12/24 - 2/18/24</title>
      <link>https://bit-101.com/blog/posts/2024-02-19/read-watched/</link>
      <pubDate>Mon, 19 Feb 2024 05:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-19/read-watched/</guid>
      <description>&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html&#34;&gt;https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Dithering algorithms. I implemented five of these. I wrote my own code for doing the actual work on this stuff and figured out the first couple with the help of Wikipedia. This page was great for grabbing a few more.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.karlsims.com/rd.html&#34;&gt;https://www.karlsims.com/rd.html&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Reaction-Diffusion simulation. Finally figured this one out. It took more than just this reference, but most of what I needed was here.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://mitpress.mit.edu/9780262539029/algorithms/&#34;&gt;https://mitpress.mit.edu/9780262539029/algorithms/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Book on algorithms. Not really hands on coding, but very readable. Got it Saturday and tore through a quarter of it pretty quickly.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://clig.dev/&#34;&gt;https://clig.dev/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Command Line Interface Guidelines. I like making command line programs. There is useful info here. An early excerpt I found very interesting:&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;em&gt;Traditionally, UNIX commands were written under the assumption they were going to be used primarily by other programs. They had more in common with functions in a programming language than with graphical applications.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;em&gt;Today, even though many CLI programs are used primarily (or even exclusively) by humans, a lot of their interaction design still carries the baggage of the past. It’s time to shed some of this baggage: if a command is going to be used primarily by humans, it should be designed for humans first.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Image Processing 01: Greyscale</title>
      <link>https://bit-101.com/blog/posts/2024-02-18/image-processing-01-greyscale/</link>
      <pubDate>Sun, 18 Feb 2024 08:11:15 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-18/image-processing-01-greyscale/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been creating a bunch of image processing functions for my &lt;a href=&#34;https://github.com/bit101/blcairo&#34;&gt;blcairo&lt;/a&gt; graphics library. It&amp;rsquo;s been a lot of fun and most of them have been surprisingly easy. So I thought I&amp;rsquo;d write about some of them. This all started when I when I wanted to try some dithering algorithms. Before applying the dithering, I wanted to convert the images to greyscale, so I put together a function for that.&lt;/p&gt;&#xA;&lt;h3 id=&#34;grey-vs-gray&#34;&gt;Grey vs Gray&lt;/h3&gt;&#xA;&lt;p&gt;This has always bothered me. In the back of my mind, I always thought there was some rule about the spelling of this word. And I was the only one who didn&amp;rsquo;t know the rule. It turns out that either one is correct. If you&amp;rsquo;re British, you&amp;rsquo;ll probably write about the colour grey. If you&amp;rsquo;re in the US, it&amp;rsquo;s the color gray. In my codebase, I use both versions all over the place. So in this article, I&amp;rsquo;ll give a shout out to my UK friends doing maths on the lifts on the way to the loo before heading back to their flats. Grey it is.&lt;/p&gt;&#xA;&lt;p&gt;Actually, per usage stats I&amp;rsquo;ve seen, it seems that grayscale wins handily over gray-scale, gray scale, greyscale, grey-scale, or grey scale. But I&amp;rsquo;m sticking to my greyscale guns.&lt;/p&gt;&#xA;&lt;h3 id=&#34;greyscale-conversion&#34;&gt;Greyscale conversion&lt;/h3&gt;&#xA;&lt;p&gt;Here, we&amp;rsquo;re going to loop through every pixel in an image and convert it to a shade of grey based on the three RGB color channels.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m going to assume that you can get an array of bytes that represents your image. HTML&amp;rsquo;s canvas can do this, as well as anything based on Cairographics. This array will contain whole numbers in the range of 0-255. There will probably be four elements for each pixel - red, green, blue and alpha. They might be arranged that way - RGBA. That&amp;rsquo;s how HTML&amp;rsquo;s canvas works. But they might be in BGRA order which is how Cairographics works. Another system might do something else like ARGB. You&amp;rsquo;ll have to do the research for your platform. In this case, all we really need to know is which one represents the alpha channel because we&amp;rsquo;re not going to mess with that one, only the red, green and blue channels. We want to average them out somehow and set all of them to be equal. So say we have an array of bytes called &lt;code&gt;data&lt;/code&gt;, we can loop through the values like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; data.length; i &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// data[i] is one color channel. Probably red or blue.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// data[i+1] is one color channel. Probably green.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// data[i+2] is one color channel. Probably blue or red.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// data[i+3] is probably the alpha channel&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s assume the last one is alpha, so we just need to deal with the first three. We want to set them all to the same value. There are a few strategies here. We could just choose one channel and set the others to be equal to it, like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; data.length; i &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; data[i]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; data[i]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// you&amp;#39;ll now have to put the updated data array back into the image to have it reflect the changes.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Assuming the first channel is red, this sets the green and blue channels to that value. This will work, but you&amp;rsquo;ll get very different results for different images. An image with a lot of reds in it will appear very light, like so:&lt;/p&gt;&#xA;&lt;p&gt;Source:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;watermelon slice and cup of red juice&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_01.png&#34; /&gt;&#xA;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/watermelon-shake-filled-glass-cup-beside-sliced-watermelon-fruit-on-brown-surface-1337825/&#34;&gt;Photo by Bruno Scramgnon&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Red channel copied to green and blue:&lt;/p&gt;&#xA;&lt;img alt=&#34;watermelon slice and cup of red juice - almost all white&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_01_a.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;However an image with less red will wind up very dark.&lt;/p&gt;&#xA;&lt;p&gt;Source:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;blueberries&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_02.png&#34; /&gt;&#xA;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/blueberry-fruit-on-gray-container-70862/&#34;&gt;Photo by veeterzy&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Red channel copied to green and blue:&lt;/p&gt;&#xA;&lt;img alt=&#34;dark blueberries&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_02_a.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Another option would just be to average the three channels. Add them up and divide by three.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; data.length; i &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    grey &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; (data[i] &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;]) &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data[i] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; grey&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; grey&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; grey&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above code is very simplified. If your &lt;code&gt;data&lt;/code&gt; array really is made of the data type &lt;code&gt;byte&lt;/code&gt;, adding three bytes together will overflow the 256-value limit and give you some strange results. In that case, you&amp;rsquo;ll need to convert to a larger data type such as &lt;code&gt;int&lt;/code&gt; or &lt;code&gt;float&lt;/code&gt;, do the math, then convert back to byte. Or, separately divide each one by three and then add the results together.&lt;/p&gt;&#xA;&lt;p&gt;This gives us:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;watermelon slice and cup of red juice, greyscale&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_01_b.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;blueberries, greyscale&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_02_b.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;This is better, but kind of muddy. It has to do with the way the human eye (and brain) perceives different color frequencies. We see green as being much brighter than blue, with red somewhere in between. So just averaging the three channels doesn&amp;rsquo;t give us the results we want. We have to take those human perception biases and account for them somehow. Luckily, a lot of research has been done on this and we have values for each channel that represent our way of seeing things. Of course, this isn&amp;rsquo;t an exact science since everyone&amp;rsquo;s eyes and brains are slightly different, but here are some numbers that are pretty well accepted and give decent results:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Multiply the red channel by 0.299&lt;/li&gt;&#xA;&lt;li&gt;Multiply the green channel by 0.587&lt;/li&gt;&#xA;&lt;li&gt;Multiply the blue channel by 0.113&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Add these together and you&amp;rsquo;ll get a decent greyscale value. You might find other values listed in other articles. Feel free to try them out and use whatever you think looks best. Here&amp;rsquo;s some pseudocode for this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; data.length; i &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    grey &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; data[i] &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.299&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.587&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.113&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data[i] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; grey&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; grey&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data[i&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; grey&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This assumes your bytes are in RGBA order. Also, this is again simplified in regards to data types. You&amp;rsquo;ll probably need to do some conversion to multiply the bytes by floats and then convert back to reset the &lt;code&gt;data&lt;/code&gt; elements. I know you can figure that out.&lt;/p&gt;&#xA;&lt;p&gt;Now we get:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;watermelon slice and cup of red juice - greyscale&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_01_c.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;blueberries, greyscale&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_02_c.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a montage of each of the results to get a better view:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;watermelon slice and cup of red juice montage&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/montage_01.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;blueberries montage&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/montage_02.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;You might not see a whole lot of difference in the final two images. In fact, you might even like the straight average better than the perceptual average. But bear in mind, both of these images are very biased towards a single color. Let&amp;rsquo;s try an image with more varied color.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;tea set with flowers&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_03.png&#34; /&gt;&#xA;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/a-cup-of-tea-teapot-and-flowers-on-the-table-19040147/&#34;&gt;Photo by Gülşah Aydoğan&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;img alt=&#34;tea set with flowers greyscale&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_03_a.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;tea set with flowers greyscale&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_03_b.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;I won&amp;rsquo;t tell you what to think, but for me, the last image - perceptual greyscale - has more contrast and better details and reflects the feel of the original better.&lt;/p&gt;&#xA;&lt;p&gt;Actually, here&amp;rsquo;s a video of the two images toggling back and forth. The slightly brighter one is the perceptual average version.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;tea set with flowers toggling&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-02-18/image_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Now you can clearly see that the center of the flowers &amp;ldquo;pop&amp;rdquo; a lot more, and the green background is lighter with more contrast.&lt;/p&gt;&#xA;&lt;p&gt;Of course, simple greyscale conversion is only the beginning. Later we&amp;rsquo;ll look at doing gamma correction, adjusting brightness and changing contrast, which can all have an effect on how the final greyscale image looks.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/person-walking-on-street-3325720/&#34;&gt;Header photo by João Cabral&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Super Triangle</title>
      <link>https://bit-101.com/blog/posts/2024-02-11/supertriangle/</link>
      <pubDate>Sun, 11 Feb 2024 12:22:58 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-11/supertriangle/</guid>
      <description>&lt;h3 id=&#34;delaunay-triangulation&#34;&gt;Delaunay Triangulation&lt;/h3&gt;&#xA;&lt;p&gt;Recently I was working on implementing Delaunay triangulation using the &lt;a href=&#34;https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm&#34;&gt;Bowyer-Watson algorithm&lt;/a&gt;. I used the exact steps outlined in that link and, while fairly complex, it worked perfectly. There was just one point that was a bit more vague than the others.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;add super-triangle to triangulation // must be large enough to completely contain all the points in pointList&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;making-the-super-triangle&#34;&gt;Making the super Triangle&lt;/h3&gt;&#xA;&lt;p&gt;To visualize what we need, here&amp;rsquo;s an idea of what I had in front of me. I have a bunch of random points (actually created through Poisson disk sampling). These were distributed across the full canvas like the rectangle shown here:&lt;/p&gt;&#xA;&lt;img alt=&#34;random points in a rectangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-11/supertri01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;For the first step of the formula, I needed a triangle that completely encompassed all of these points. To get things working at first, I just made a really huge triangle. But &amp;ldquo;really huge&amp;rdquo; wasn&amp;rsquo;t very mathematical. I tried creating increasingly larger triangles until all points tested as inside the triangle, but that was very brute-force and inelegant. I saw one method where you calculate the convex hull of all the points and iteratively use the points of that to create triangles and check the area of each triangle until you have the one with the smallest area. Neat, but WAY overkill, imo.&lt;/p&gt;&#xA;&lt;p&gt;I had an idea in my head that if I could take the two left points of the &lt;em&gt;rectangle&lt;/em&gt; and extend them up and down some amount, and then take the right midpoint of the rectangle and extend that more to the right some amount, I should wind up with a triangle that fit the rectangle.&lt;/p&gt;&#xA;&lt;p&gt;My first shot at was to extend the left points up and down one screen height, and extend the right midpoint to the right one-half screen width. Using this formula:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;triangle(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x, y &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; h,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x, y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; h &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; w &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1.5&lt;/span&gt;, y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; h &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are the top left corner of the rectangle (often 0, 0), and &lt;code&gt;w&lt;/code&gt; and &lt;code&gt;h&lt;/code&gt; are the width and height of the rectangle (generally the canvas size). I gave that a go and &amp;hellip; lo and behold, it worked perfectly!&lt;/p&gt;&#xA;&lt;img alt=&#34;random points in a rectangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-11/supertri02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I was sure I just got lucky with my measurements, so I tried changing the size of the rectangle&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;random points in a rectangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-11/supertri03.png&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;random points in a rectangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-11/supertri04.png&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;And it kept working perfectly. I love it when that happens.&lt;/p&gt;&#xA;&lt;p&gt;Now, I&amp;rsquo;m not saying that this is the &lt;em&gt;only&lt;/em&gt; triangle you can use, or even that it&amp;rsquo;s the &lt;em&gt;best&lt;/em&gt; one, or even the one with the smallest area or &amp;hellip; really anything other than:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;It works.&lt;/li&gt;&#xA;&lt;li&gt;It&amp;rsquo;s super easy to calculate.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I had one tiny concern though. There&amp;rsquo;s a chance that one of the points could end up at exactly &lt;code&gt;x = 0.0&lt;/code&gt; or close enough to potentially cause some kind of problem. I&amp;rsquo;m not entirely sure it would cause a problem, but I wanted to play it safe and give myself a little leeway just in case. So I made the triangle just a little bit larger be subtracting a bit off the &lt;code&gt;x&lt;/code&gt; values on the left, and adding a bit on the right. Like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;triangle(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; w &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.1&lt;/span&gt;, y &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; h,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; w &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.1&lt;/span&gt;, y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; h &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; w &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1.7&lt;/span&gt;, y &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; h &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s the result:&lt;/p&gt;&#xA;&lt;img alt=&#34;random points in a rectangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-11/supertri05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now there&amp;rsquo;s no doubt at all that every point is safely &lt;em&gt;inside&lt;/em&gt; the triangle.&lt;/p&gt;&#xA;&lt;p&gt;In my library I made a function that iterates over the point list finding the min and max x and y values and uses that to create the triangle. It&amp;rsquo;s a bit more complex but it saves me from having to manually pass in the values to create the triangle. I just pass in the point list and all is done for me.&lt;/p&gt;&#xA;&lt;p&gt;By the way, this is the result of Delaunay triangulation, which I&amp;rsquo;ll probably write something about at some point.&lt;/p&gt;&#xA;&lt;img alt=&#34;random points in a rectangle&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-11/delaunay01.png&#34; /&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Age</title>
      <link>https://bit-101.com/blog/posts/2024-02-11/age/</link>
      <pubDate>Sun, 11 Feb 2024 09:35:08 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-11/age/</guid>
      <description>&lt;p&gt;I remember the year I turned 29. I kind of freaked out because that meant I was only a year away from being &amp;ldquo;old&amp;rdquo;. But when I turned 30 I was like, meh, no big deal.&lt;/p&gt;&#xA;&lt;p&gt;The same thing has happened every decade since. Last year I hit 59. Oh my god, I&amp;rsquo;m going to be 60 soon. Four months have gone by and as usual I&amp;rsquo;m starting to get used to the idea and even embracing it. Sixty. Hell yeah!&lt;/p&gt;&#xA;&lt;p&gt;When I was young and went to my grandparents house every weekend, they were in their 60s and I thought they were OLD. I do not feel as old as I thought they were at the time. The man in the mirror looks pretty good for his age. But sometimes I see a picture of myself and I&amp;rsquo;m shocked to see my grandfather there instead of me. Funny how photos and mirrors seem so different. Some kind of psychology going on there.&lt;/p&gt;&#xA;&lt;p&gt;Physically, I&amp;rsquo;m doing pretty damn well. I&amp;rsquo;m actively running. My weight is stably in the best place it&amp;rsquo;s been ever. BMI in the right place and all that. Just had my annual physical and the blood pressure and all the blood chemicals are just where they should be. The usual complaints here and there, but if I&amp;rsquo;m honest, no worse than any other decade. I certainly feel FAR better than 16-17 years ago, when I was 60 pounds heavier than I am now.&lt;/p&gt;&#xA;&lt;p&gt;Mentally I think I&amp;rsquo;m pretty good. I find my memory isn&amp;rsquo;t the photographic wunderkind it used to be. And I struggle to find the word I&amp;rsquo;m looking for more than I think I used to. But that&amp;rsquo;s all normal stuff.&lt;/p&gt;&#xA;&lt;p&gt;But, more and more I have to face the fact that I&amp;rsquo;m not immortal. Maybe I have another 30 something years left. More likely 20 something. That seems startlingly few. But I was way more obsessed with my mortality and how long I had left when I was in my 30s and 40s than I am now. More than being afraid of dying I am sad that I will eventually have to stop experiencing life. Because life is an amazing gift and even thought it&amp;rsquo;s not always perfect I love being part of it. This sentiment is expressed succinctly by Hemingway in &lt;em&gt;For Whom the Bell Tolls&lt;/em&gt; in an untitled chapter often referred to as &amp;ldquo;Sordo&amp;rsquo;s Last Stand&amp;rdquo;. Sordo is a guerrilla fighter in the Spanish Civil War. He and his group are under attack and surrounded on a hill. He knows there&amp;rsquo;s no way out. He will die on this hill.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Dying was nothing and he had no picture of it nor fear of it in his mind. But living was a field of grain blowing in the wind on the side of a hill. Living was a hawk in the sky. Living was an earthen jar of water in the dust of the threshing with the grain flailed out and the chaff blowing. Living was a horse between your legs and a carbine under one leg and a hill and a valley and a stream with trees along it and the far side of the valley and the hills beyond.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not religious, so I don&amp;rsquo;t &lt;em&gt;believe&lt;/em&gt; in heaven or hell. As to what happens after, I have no idea. But it&amp;rsquo;s nothing I worry about. If anything, I&amp;rsquo;m just jealous of all those who will go on experiencing life after me.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, this post wasn&amp;rsquo;t supposed to be morbid. More of a memento mori. Knowing that I have limited time here makes me appreciate what time I have left even more. And I&amp;rsquo;m trying to make that time mean something. Sometimes I think about wanting to leave some kind of legacy. That&amp;rsquo;s hard in technology where everything changes so fast. I&amp;rsquo;ve written a bunch of books, but most of them are out of print and based on a language and platform that no longer exists. That&amp;rsquo;s why I try to write more in pseudocode these days. The concepts and algorithms will live a lot longer than the syntax. In any case, I know that my writings and videos and open source code and courses and conference talks have helped many people. It still brings me a lot of joy when someone reaches out these days and say that my work helped them started in tech, or helped them get through school or in their first job. And I hope that some will pay it forward and keep it going.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a lot of messed up things in this world and a lot of bad things to protest and fight against. Sometimes I fear that we&amp;rsquo;ll all be so focused on fighting the bad that we&amp;rsquo;ll forget to do good. It&amp;rsquo;s not the same thing. Going around punching Nazis might be necessary, but it doesn&amp;rsquo;t - by itself - make the world a better place. Someone has to deliberately pump some positivity into the world as well. Weeding your garden doesn&amp;rsquo;t do any good if you forget to actually plant some flowers or vegetables too.&lt;/p&gt;&#xA;&lt;p&gt;So I continue to try and learn and share what I learn in a way that hopefully lets the next person learn that thing with a bit less effort than it took me to learn it. In fact, now that I just wrote that sentence, I realize that&amp;rsquo;s kind of been my mission statement for the past 25 years. So I&amp;rsquo;m going to keep on doing that as much as I can for as long as I can and hope that one or several people not only learn something but get inspired to do the same.&lt;/p&gt;&#xA;&lt;p&gt;So we&amp;rsquo;ll end on a bright spot!&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/old-paper-calendar-11177238/&#34;&gt;Photo by Hatice Baran&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Read/Watched: 2/05/24 - 2/11/24</title>
      <link>https://bit-101.com/blog/posts/2024-02-11/read-watched/</link>
      <pubDate>Sun, 11 Feb 2024 05:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-11/read-watched/</guid>
      <description>&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=cKOSJ5AAwfc&#34;&gt;https://www.youtube.com/watch?v=cKOSJ5AAwfc&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Betty Boop: Snow White, 1933. What an amazing piece of animation.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=qXZRWa8kyAE&#34;&gt;https://www.youtube.com/watch?v=qXZRWa8kyAE&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The previous video led me on a Cab Calloway rabbit hole. Which led me to this video, where I discovered the Nicholas Brothers. My jaw hung open through most of their dance routine.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/jasonwebb/morphogenesis-resources&#34;&gt;https://github.com/jasonwebb/morphogenesis-resources&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;An amazing list of resources on &lt;a href=&#34;https://en.wikipedia.org/wiki/Digital_morphogenesis&#34;&gt;digital morphogenesis&lt;/a&gt;. I&amp;rsquo;ve explored a lot of these in depth over the years and some I&amp;rsquo;ve touched on only lightly. Others have been on my todo list for a while. And some are new to me. This will be a dangerous rabbit hole.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Read/Watched: 1/29/24 - 2/04/24</title>
      <link>https://bit-101.com/blog/posts/2024-02-04/read-watched/</link>
      <pubDate>Mon, 05 Feb 2024 05:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-04/read-watched/</guid>
      <description>&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=EGvuSOvuREQ&#34;&gt;Shader Workshop&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Shader workshop.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.futilitycloset.com/2024/01/14/the-house-dogs-grave/&#34;&gt;The House Dog&amp;rsquo;s Grave&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I&amp;rsquo;m the last person to ever respond to poetry. I&amp;rsquo;ve tried. It usually does nothing for me. This one destroyed me.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.usememos.com/&#34;&gt;Memos&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Been looking for a self-hosted Google Keep replacement. We&amp;rsquo;ll see how this goes.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://medium.com/@jason.webb/space-colonization-algorithm-in-javascript-6f683b743dc5&#34;&gt;Modeling organic branching structures with the space colonization algorithm and JavaScript&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;This has been on my read later list for a while. Finally started digging into it.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>FOTD: RoundTo, RoundNearest</title>
      <link>https://bit-101.com/blog/posts/2024-02-02/fotd-round/</link>
      <pubDate>Fri, 02 Feb 2024 05:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-02/fotd-round/</guid>
      <description>&lt;p&gt;Rounding floating point numbers is a pretty common need. Occasionally needed in calculations, but way more common in displaying numbers. You don&amp;rsquo;t want to be telling someone that two locations are 7.394739782 miles apart. 7.4 is probably fine. And 7.39 will satisfy most anyone. So let&amp;rsquo;s make a function that will let you round to any level of precision.&lt;/p&gt;&#xA;&lt;h3 id=&#34;roundto&#34;&gt;RoundTo&lt;/h3&gt;&#xA;&lt;p&gt;Rounding a floating point number to a whole number is generally built in to most math libraries. We&amp;rsquo;ll use the magic number 3.14159 for our examples.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;round(&lt;span style=&#34;color:#f60&#34;&gt;3.14159&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 3.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want to round to one decimal place:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;First multiply by 10 to get 31.4159&lt;/li&gt;&#xA;&lt;li&gt;Then round to get 31.0&lt;/li&gt;&#xA;&lt;li&gt;Finally, divide by 10 for 3.1&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For two decimal places:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Multipy by 100 = 314.159&lt;/li&gt;&#xA;&lt;li&gt;Round = 314.0&lt;/li&gt;&#xA;&lt;li&gt;Divide by 100 = 3.14&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;And so on. Multiply and divide by 1000 to get 3.142 (the final 1 gets rounded up to 2).&lt;/p&gt;&#xA;&lt;p&gt;So, if we want to be able to round to an arbitrary number of decimal places, we need to be able to equate:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;1 = 10&lt;/li&gt;&#xA;&lt;li&gt;2 = 100&lt;/li&gt;&#xA;&lt;li&gt;3 = 1000&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The function is &lt;code&gt;pow&lt;/code&gt;. It&amp;rsquo;s in most math libraries and raised a number to a certain power. So we can say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pow(&lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;//   10&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pow(&lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;//  100&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pow(&lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 1000&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// etc.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In fact, &lt;code&gt;pow(10, 0)&lt;/code&gt; equals 1. This means multiply by 1, round, divide by 1, which is the same as just rounding to zero decimal places.&lt;/p&gt;&#xA;&lt;p&gt;Since we&amp;rsquo;ll be using the value twice, we might as well store it. So we get:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; roundTo(val, places) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mult &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; pow(&lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;, places)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    val &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; val &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; mult&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    val &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; round(val)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    val &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; val &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; mult&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; val&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I wrote it out line by line for clarity, but you can shorten this to:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; roundTo(val, places) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mult &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; pow(&lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;, places)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; round(val &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; mult) &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; mult&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And testing this&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundTo(&lt;span style=&#34;color:#f60&#34;&gt;3.14159&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 3.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundTo(&lt;span style=&#34;color:#f60&#34;&gt;3.14159&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 3.1&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundTo(&lt;span style=&#34;color:#f60&#34;&gt;3.14159&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 3.14&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundTo(&lt;span style=&#34;color:#f60&#34;&gt;3.14159&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 3.142&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s another bit of magic here though. It works with negative numbers too!&lt;/p&gt;&#xA;&lt;p&gt;It happens that &lt;code&gt;pow(10, -1)&lt;/code&gt; is 0.1. Take a number like 6789 and multiply by 0.1 and you get 678.9. Round it to 679 and divide by 0.1 to get 6790. You just rounded to the nearest tens place. So&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundTo(&lt;span style=&#34;color:#f60&#34;&gt;6789&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 6790&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundTo(&lt;span style=&#34;color:#f60&#34;&gt;6789&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 6800&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundTo(&lt;span style=&#34;color:#f60&#34;&gt;6789&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 7000&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;roundnearest&#34;&gt;RoundNearest&lt;/h3&gt;&#xA;&lt;p&gt;Since that one was pretty easy, here&amp;rsquo;s a related one. Say instead of rounding to the nearest power of ten, you wanted to round to the nearest multiple of a number.&lt;/p&gt;&#xA;&lt;p&gt;For example:&lt;/p&gt;&#xA;&lt;p&gt;7 rounded to the nearest 3 would be 6.&#xA;8 rounded to the nearest 3 would be 9.&#xA;7 rounded to the nearest 4 would be 8.&lt;/p&gt;&#xA;&lt;p&gt;This one is even simpler to code than &lt;code&gt;roundTo&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; roundToNearest(val, mult) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; round(val &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; mult) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; mult&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And some examples:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundNearest(&lt;span style=&#34;color:#f60&#34;&gt;7&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundNearest(&lt;span style=&#34;color:#f60&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 9&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;roundNearest(&lt;span style=&#34;color:#f60&#34;&gt;7&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;4&lt;/span&gt;) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 8&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You might wonder when you&amp;rsquo;d need something like this. It&amp;rsquo;s good for quantizing values, or creating stairstepping kind of functions. Say I&amp;rsquo;m plotting values across a 400x400 pixel canvas with x going from 0 to the width of the canvas. And y will be the height of the canvas minus x.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; height &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; x&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    circle(x, y, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gets us something like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;a linear graph&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-02/round01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now let&amp;rsquo;s quantize the y value so it&amp;rsquo;s always a multiple of 40.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; x &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; width; x &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; height &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; x&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; roundNearest(y, &lt;span style=&#34;color:#f60&#34;&gt;40&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    circle(x, y, &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result:&lt;/p&gt;&#xA;&lt;img alt=&#34;a linear graph&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-02/round02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;An even more useful example would be quantizing the colors of an image. Say each channel of each pixel had a value from 0 to 255. You could say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;value &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; roundNearest(value, &lt;span style=&#34;color:#f60&#34;&gt;64&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now each value would be 0, 64, 128, 192 or 256. You&amp;rsquo;d have to &lt;code&gt;clamp&lt;/code&gt; that last value to have it equal 255. Now you&amp;rsquo;ve reduced the values from 256 to 5. It might not be the best way to go about this, but it should give you an idea of how you can apply the function.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Genuary Recap</title>
      <link>https://bit-101.com/blog/posts/2024-02-01/genuary-recap/</link>
      <pubDate>Thu, 01 Feb 2024 08:47:55 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-01/genuary-recap/</guid>
      <description>&lt;p&gt;I know, I just did a post entitled &lt;a href=&#34;https://bit-101.com/blog/posts/2024-02-01/genuary-wrap/&#34;&gt;Genuary Wrap&lt;/a&gt;. So what&amp;rsquo;s this all about?&lt;/p&gt;&#xA;&lt;p&gt;Well, mostly, the wrap was just about my last six entries. Every week I did a post showing the previous six or seven entries, so I just wanted to wrap up that series.&lt;/p&gt;&#xA;&lt;p&gt;But Genuary 2024 was a pretty intense experience for me, and I wanted to say a few words about it as a whole. And there is one piece I wanted to do a deeper dive into. I could have actually stretched this into two more posts, but I need to move on.&lt;/p&gt;&#xA;&lt;h3 id=&#34;thoughts-on-genuary-2024&#34;&gt;Thoughts on Genuary 2024&lt;/h3&gt;&#xA;&lt;p&gt;As I just said, this was pretty intense. Thirty one prompts. Some were easy. Some were hard. Some were super interesting and I was excited to do them. Some I really didn&amp;rsquo;t want to go near. But it was good to get outside of my comfort zone.&lt;/p&gt;&#xA;&lt;p&gt;Some of the prompts I liked the least were &amp;ldquo;do something in this particular style&amp;rdquo; or &amp;ldquo;&amp;hellip; in the style of this particular artist.&amp;rdquo; It felt a bit too art-schooly. But OK, I did them and got something out of each one.&lt;/p&gt;&#xA;&lt;p&gt;I do like the fact that the rules are so loose. &amp;ldquo;You don’t have to follow the prompt exactly. Or even at all. But, y’know, we put effort into this.&amp;rdquo; There were definitely days where I stretched the bounds of the prompt almost beyond recognition. I just took things where they led me.&lt;/p&gt;&#xA;&lt;p&gt;Here are some of the things I learned, discovered, re-learned, re-discovered, took new deep dives into, etc.:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;OpenGL and shaders - in Go! Big deep dive into this.&lt;/li&gt;&#xA;&lt;li&gt;Signed Distance Functions. I still haven&amp;rsquo;t figured out what to use these for, but they are neat.&lt;/li&gt;&#xA;&lt;li&gt;Poisson Disk Sampling. I forget how I wandered into this, but I figured it out, added it to my library and you&amp;rsquo;ll see it in multiple entries.&lt;/li&gt;&#xA;&lt;li&gt;Easing functions. I put these in my library a year or two ago, but never used them. They got a lot of use this month.&lt;/li&gt;&#xA;&lt;li&gt;Scenes and acts. A couple of new paradigms in my rendering engine. It&amp;rsquo;s going to make it way simpler to do larger, more complex videos this year. This is one of my goals and the idea of multi-scene videos is one that I&amp;rsquo;ve been trying to work out for some time now. I think I&amp;rsquo;m pretty close to nailing a really usable sytem for this.&lt;/li&gt;&#xA;&lt;li&gt;Sound and music. Supercollider. I did a big deep dive into this last year, but it&amp;rsquo;s been on the back burner for a few months. At the end of 2023, I kind of clarified (for myself) my musical goals. I don&amp;rsquo;t necessarily just want to make songs. I want do be able to work with sound and music as accompaniments for visual pieces - videos. So I actually did that this month - in two pieces.&lt;/li&gt;&#xA;&lt;li&gt;Isometrics. I had an old, unused library for this, and started to revive it. Hope to do more with it later.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;day-28---skeuomorphism&#34;&gt;Day 28 - Skeuomorphism&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day28.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This was my favorite piece of the month, and probably the one I spent the most time on. There&amp;rsquo;s a lot going on here, and a lot that went into it, so I wanted to deconstruct it a little.&lt;/p&gt;&#xA;&lt;p&gt;The prompt is &amp;ldquo;skeuomophism&amp;rdquo;. Here&amp;rsquo;s a definition:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A skeuomorph &amp;hellip; is a derivative object that retains ornamental design cues (attributes) from structures that were necessary in the original.[3] Skeuomorphs are typically used to make something new feel familiar in an effort to speed understanding and acclimation. They employ elements that, while essential to the original object, serve no pragmatic purpose in the new system.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;From &lt;a href=&#34;https://en.wikipedia.org/wiki/Skeuomorph&#34;&gt;https://en.wikipedia.org/wiki/Skeuomorph&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;I wasn&amp;rsquo;t sure what I wanted to do for this. At some point I ran across this image:&lt;/p&gt;&#xA;&lt;img alt=&#34;steamboat willie frame&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-01/steamboat_01.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;From &lt;a href=&#34;https://hackaday.com/2024/01/24/steamboat-willie-never-sounded-better/&#34;&gt;https://hackaday.com/2024/01/24/steamboat-willie-never-sounded-better/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This is a frame from Steamboat Willie, which recently went into the public domain, so is seeing a lot of use. I was interested in the jagged shape on the right edge. It&amp;rsquo;s the soundtrack, optically encoded into the film.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;d thought of doing stuff like that before - encoding audio in a bitmap frame. Currently, I don&amp;rsquo;t have a way to analyze or play back audio in my Go-based workflow. There are libraries for that, but I didn&amp;rsquo;t want to go that deep for this piece. But I thought, what else can I encode in a bitmap? How about another bitmap??? Crazy, right?&lt;/p&gt;&#xA;&lt;p&gt;The skeuomophism became replicating reading an image off of a tape drive. Rather than encoding the image as spots of magnetic charges, I wanted to try encoding the bits in pixels. Insane, but worth trying.&lt;/p&gt;&#xA;&lt;p&gt;First I built an encoder. It takes a 100x100 pixel, grayscale image and loops through each pixel. Since it&amp;rsquo;s grayscale, I can get each pixel as a 256 level value. One byte. Eight bits. Each bit is a black or white pixel. Each byte goes into one column of a 10,000 by 8 pixel image.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://bit-101.com/blog2024_images/2024-02-01/encoded.png&#34;&gt;Here&amp;rsquo;s a link to that image&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a 800-pixel wide crop of that image:&lt;/p&gt;&#xA;&lt;img alt=&#34;encoded data&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-01/encoded800.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And here it is zoomed in 10x:&lt;/p&gt;&#xA;&lt;img alt=&#34;encoded data&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-01/encoded800x10clear.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You can see each &lt;em&gt;column&lt;/em&gt; of the image is made up of eight pixels that are either black or white and represent one bit of that 8-bit byte.&lt;/p&gt;&#xA;&lt;p&gt;With that in place, came the decoding.&lt;/p&gt;&#xA;&lt;p&gt;First, I loaded the encoded data - the virtual &amp;ldquo;tape&amp;rdquo; containing the encoded bits of the image. I wanted to display it as it was reading, so I again scaled it by 10x. But it came out looking like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;encoded data&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-02-01/encoded800x10.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Not great. It&amp;rsquo;s got to do with the filter used in scaling. The default one in the Cairographics library interpolates, which usually results in a better result. But zooming in on pixels, you need to use a different filter. Unfortunately, my library had not implemented the different scaling filters. So I had to build out the C library wrappers for those. Wasn&amp;rsquo;t a big deal and now I have them there if I need them in the future.&lt;/p&gt;&#xA;&lt;p&gt;Now I just go through the encoded bitmap, column by column. I sample each pixel in each column, turn them into bits, then into a single byte, set the color value based on that byte, and draw a single pixel.&lt;/p&gt;&#xA;&lt;p&gt;I visually scrolled the encoded bitmap, but that&amp;rsquo;s just for show - so you can see which column is currently being read.&lt;/p&gt;&#xA;&lt;p&gt;The final movie runs at 60fps, and it&amp;rsquo;s processing one pixel per frame, so it&amp;rsquo;s pretty damn slow. Just like reading data off of a tape drive.&lt;/p&gt;&#xA;&lt;p&gt;So, like a true skeuomorph, this whole process serves no pragmatic purpose whatsoever. It&amp;rsquo;s literally encoding a bitmap into another bitmap that is eight times as large. Eight times larger in dimensions, though only 1k bigger in disk size. But it was a fun project and offered several cool challenges and people seemed to like it a lot on &lt;a href=&#34;https://mstdn.social/@bit101/111833533449283036&#34;&gt;mastodon&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Genuary Wrap</title>
      <link>https://bit-101.com/blog/posts/2024-02-01/genuary-wrap/</link>
      <pubDate>Thu, 01 Feb 2024 07:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-02-01/genuary-wrap/</guid>
      <description>&lt;h3 id=&#34;day-26---grow-a-seed&#34;&gt;Day 26 - Grow a seed&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day26.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Starts with a seed. It grows up and curls. At random points, it branches off. And on and on. Fades out in the end - I&amp;rsquo;ve been obsessed with smooth loops. Fading out before starting over is one way to deal with that.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-27---code-for-one-hour&#34;&gt;Day 27 - Code for one hour&lt;/h3&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/genuary2024/day27.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I had an isometric (bimetric technically) library kicking around. I dusted it off and started adding it to my main &lt;a href=&#34;https://github.com/bit101/bitlib&#34;&gt;bitlib&lt;/a&gt; library. I used Poisson Disk Sampling with different radii for shading on the sides, which was neat.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-28---skeuomorphism&#34;&gt;Day 28 - Skeuomorphism&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day28.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I think this is my best piece of the month. It requires a little (lot) of explanation. So I think I&amp;rsquo;ll save that for another whole post.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-29---signed-distance-functions&#34;&gt;Day 29 - Signed Distance Functions&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day29.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This prompt was in last year&amp;rsquo;s Genuary too. I was not happy with my example for that last year and not really happy with it this year. I did start adding SDFs to my main library, but I haven&amp;rsquo;t really figured out what to do with them yet. This was just a lot of random experimenting until I came up with something that looked interesting. I think it&amp;rsquo;s kind of using an SDF to create a flow field and drawing lines based on that. Meh.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-30---shaders&#34;&gt;Day 30 - Shaders&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day30.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This was done with a shader. That&amp;rsquo;s about all it&amp;rsquo;s got going for it. It did get me learning more about how to set up an OpenGL project, and I&amp;rsquo;ve been documenting my learning here: &lt;a href=&#34;https://github.com/bit101/glsteps&#34;&gt;glsteps&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-31---generative-music&#34;&gt;Day 31 - Generative music&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;controls&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day31.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;One of my goals this year is to do longer, multi-scene videos with soundtracks. I totally rushed every part of this, but figured out a lot of the mechanics of how to accomplish this. Big learning on this: recently I added &amp;ldquo;scenes&amp;rdquo; to my rendering library. This was cool, but it was still all one big render. So if I wanted to change one parameter in scene 10, I still had to re-render every frame of every scene. With this piece I realized it&amp;rsquo;s going to be way more efficient to render scenes in chunks, export the video for each chunk and stitch them together at the end with ffmpeg. Here, I rendered anywhere from two to five scenes at a go. I started calling each chunk an &amp;ldquo;act&amp;rdquo;. This was all kind of ad-hoc, but I think I&amp;rsquo;ll adjust my library to allow for acts and scenes.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Read/Watched: 1/22/24 - 1/28/24</title>
      <link>https://bit-101.com/blog/posts/2024-01-29/read-watched/</link>
      <pubDate>Mon, 29 Jan 2024 12:15:31 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-29/read-watched/</guid>
      <description>&lt;p&gt;A new feature inspired by &lt;a href=&#34;https://arpitonline.com/2024/01/28/things-i-read-this-week-jan-28/&#34;&gt;Arpit Mathur&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=ikF0pZqpfYw&#34;&gt;https://www.youtube.com/watch?v=ikF0pZqpfYw&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Uconsole. Trying to convince myself not to buy one of these.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://learnopengl.com/Introduction&#34;&gt;https://learnopengl.com/Introduction&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Been doing a deep dive into OpenGL. This is a good resource.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl&#34;&gt;https://kylewbanks.com/blog/tutorial-opengl-with-golang-part-1-hello-opengl&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The OpenGL I&amp;rsquo;ve been doing has been in Go with the &lt;code&gt;go-gl&lt;/code&gt; library. This article was indispensible.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.quantamagazine.org/the-quest-to-decode-the-mandelbrot-set-maths-famed-fractal-20240126/&#34;&gt;https://www.quantamagazine.org/the-quest-to-decode-the-mandelbrot-set-maths-famed-fractal-20240126/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I&amp;rsquo;m a sucker for an article on fractals. I thought this would be a fluff piece, but it&amp;rsquo;s actually a long, interesting read.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://wedistribute.org/2023/11/introducing-faircamp/&#34;&gt;https://wedistribute.org/2023/11/introducing-faircamp/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Interesting concept. Bills itself as a free alternative to Bandcamp, but it&amp;rsquo;s actually a static site generator aimed at musicians. Not sure if I fully understand it just yet.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://buttondown.email/ownyourweb/archive/issue-08/&#34;&gt;https://buttondown.email/ownyourweb/archive/issue-08/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I&amp;rsquo;ve been following this series. It&amp;rsquo;s part of what got me to reboot this blog in Hugo.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/brown-wooden-ladder-on-brown-wooden-bookshelf-220326/&#34;&gt;Photo by Pixabay&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>FOTD: Wrap</title>
      <link>https://bit-101.com/blog/posts/2024-01-29/fotd-wrap/</link>
      <pubDate>Mon, 29 Jan 2024 05:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-29/fotd-wrap/</guid>
      <description>&lt;p&gt;The next Function Of The Day is &lt;code&gt;wrap&lt;/code&gt;. This one works in a similar way to &lt;code&gt;clamp&lt;/code&gt; - if the value is in the range, it does nothing. But if the value is outside the given range, it will wrap it around to the other side of the range.&lt;/p&gt;&#xA;&lt;h3 id=&#34;wrap&#34;&gt;Wrap&lt;/h3&gt;&#xA;&lt;p&gt;Sometimes you want a value to remain in a specific range, but if it goes outside that range you don&amp;rsquo;t want to just clamp it. Instead maybe you want it to come back in at the other end of the range. Think of the old computer game, Asteroids. If you fly off one edge of the screen, you reappear on the other side. That&amp;rsquo;s how wrap works.&lt;/p&gt;&#xA;&lt;p&gt;An example:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;12&lt;/span&gt;; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; wrap(i, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This should have &lt;code&gt;x&lt;/code&gt; set to &lt;code&gt;5, 6, 7, 8, 9, 0, 1, 2&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;In a simple example like this, wrap is acting as a modulus operator, just as if you said:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;12&lt;/span&gt;; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But that&amp;rsquo;s only because our range starts at zero. With &lt;code&gt;wrap&lt;/code&gt;, the range can be anything. for example&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;12&lt;/span&gt;; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; wrap(i, &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This wraps the value to always be in the range of 5 to 9. So &lt;code&gt;x&lt;/code&gt; will equal &lt;code&gt;7, 8, 9, 5, 6, 7, 8, 9, 5, 6, 7&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Note that the range is inclusive of the lower value, but exclusive of the upper value.&lt;/p&gt;&#xA;&lt;p&gt;This also needs to work with negative numbers and ranges. We have to be careful here because, oddly, the built-in modulus function (usually &lt;code&gt;%&lt;/code&gt;) works differently in different languages when it comes to using negative numbers.&lt;/p&gt;&#xA;&lt;p&gt;For example, in Go, as well as C, C++, Java, and many other languages:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;7&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;7&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, in Python and some other languages:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Python&#34; data-lang=&#34;Python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;7&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;  &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;  &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#f60&#34;&gt;7&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In case you&amp;rsquo;re interested, here&amp;rsquo;s a full write up on why it&amp;rsquo;s different:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://torstencurdt.com/tech/posts/modulo-of-negative-numbers/&#34;&gt;https://torstencurdt.com/tech/posts/modulo-of-negative-numbers/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;To be careful with this, I just avoid using the built-in modulo operator and use floored division, as defined by Donald Knuth. The floored division implementation of modulo looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; b &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; floor(a &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; b)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gives us the same results that Python gives for modulo. Whether or not you agree that&amp;rsquo;s the &amp;ldquo;correct&amp;rdquo; way, it will work the way we expect it to for wrapping, and using this definition rather than the language default operator will work the same whatever language you use it in.&lt;/p&gt;&#xA;&lt;h3 id=&#34;code&#34;&gt;Code&lt;/h3&gt;&#xA;&lt;p&gt;So finally we come to the implementation of &lt;code&gt;wrap&lt;/code&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; wrap(val, min, max) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    val &lt;span style=&#34;color:#555&#34;&gt;-=&lt;/span&gt; min&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    max &lt;span style=&#34;color:#555&#34;&gt;-=&lt;/span&gt; min&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; min &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; val &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; max &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; floor(val &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; max)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, we just subtract min from both &lt;code&gt;val&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;. This puts the range into the form of zero to whatever. Once it&amp;rsquo;s there, we can just do a modulo (using the floored division method), and then add &lt;code&gt;min&lt;/code&gt; back to the result.&lt;/p&gt;&#xA;&lt;h3 id=&#34;examples&#34;&gt;Example(s)&lt;/h3&gt;&#xA;&lt;p&gt;In this example, I made 100 random points that move left to right. There&amp;rsquo;s a 400x400 canvas, but I want them to stay in a square in the center which goes from 100 to 300 on both the x and y axes.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-29/wrap01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I drew the center rectangle and then drew each point, then added 5 pixels to each point&amp;rsquo;s x value. And then I used &lt;code&gt;wrap&lt;/code&gt; to wrap the x value to a range of 100, 300.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// for each x, y point...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;circle(x, y, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; wrap(x, &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now when a point goes beyond an x of 300, it wraps back to 100.&lt;/p&gt;&#xA;&lt;p&gt;Another way I use this function is in adjusting angles. Generally, it&amp;rsquo;s nice to have your angles in a range of 0 to 2 * PI. Or maybe -PI to PI.&lt;/p&gt;&#xA;&lt;p&gt;But sometimes you calculate an angle using some complex algorith, which may result in the angle being way outside of those ranges. Luckily angles naturally wrap around a full circle, so that 370 degrees is the same as 10 degrees, and -10 degrees is the same as 350 degrees. So our &lt;code&gt;wrap&lt;/code&gt; function is an easy way to get these into line.&lt;/p&gt;&#xA;&lt;p&gt;I made two functions that are useful for these cases.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// TAU = PI * 2 &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; wrapTau(value) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; wrap(value, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, PI &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; wrapPi(value) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; wrap(value, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;PI, PI)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I know this isn&amp;rsquo;t always necessary. Trig functions will still work with multiples of angles, but if you need to display the angle number, or use the angle in a function like &lt;code&gt;norm&lt;/code&gt; or &lt;code&gt;map&lt;/code&gt; that requires it to be within a range, this is helpful.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Blogroll</title>
      <link>https://bit-101.com/blog/pages/blogroll/</link>
      <pubDate>Sun, 28 Jan 2024 20:41:15 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/pages/blogroll/</guid>
      <description>&lt;p&gt;If you find my site interesting or useful, here are a few other sites you might also find interesting or useful. They&amp;rsquo;re not all &amp;ldquo;blogs&amp;rdquo; per se, but have quality content, imo.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.amygoodchild.com/blog&#34;&gt;Amy GoodChild&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://generative.substack.com/&#34;&gt;Generative Art Newsletter&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.gorillasun.de/&#34;&gt;Gorilla Sun Blog&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.gorillasun.de/tag/newsletter/&#34;&gt;Gorilla Sun Newsletter&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://inconvergent.net/&#34;&gt;Inconvergent&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.johndcook.com/blog/&#34;&gt;John D. Cook&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/jasonwebb/morphogenesis-resources&#34;&gt;Morphogenesis Resources&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://buttondown.email/ownyourweb&#34;&gt;Own Your Web&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.quantamagazine.org/&#34;&gt;Quanta Magazine&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://thatsmaths.com/&#34;&gt;Thats Maths&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://aperiodical.com/&#34;&gt;The Aperiodical&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/blog-letters-on-brown-wood-262508/&#34;&gt;Photo by Pixabay&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>FOTD: Clamp</title>
      <link>https://bit-101.com/blog/posts/2024-01-28/fotd-clamp/</link>
      <pubDate>Sun, 28 Jan 2024 06:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-28/fotd-clamp/</guid>
      <description>&lt;h3 id=&#34;clamp&#34;&gt;Clamp&lt;/h3&gt;&#xA;&lt;p&gt;We&amp;rsquo;ll continue the discussion of functions that work with values within ranges. The &lt;code&gt;clamp&lt;/code&gt; function is a pretty simple one. It just makes any value that&amp;rsquo;s higher than the range equal to the max value of the range, and any value that&amp;rsquo;s lower than the range equal to the min value.&lt;/p&gt;&#xA;&lt;h3 id=&#34;code&#34;&gt;Code&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; clamp(value, minimum, maximum) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (value &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; minimum) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;value &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; minimum&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (value &lt;span style=&#34;color:#555&#34;&gt;&amp;gt;&lt;/span&gt; maximum) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;value &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; maximum&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; value&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If value is either greater than &lt;code&gt;maximum&lt;/code&gt; or less than &lt;code&gt;minimum&lt;/code&gt;, it &amp;ldquo;clamps&amp;rdquo; it to be within that range.&lt;/p&gt;&#xA;&lt;p&gt;This is the brute force method. If your language has &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; functions, this can be simplified a bit. These are often part of a &lt;code&gt;math&lt;/code&gt; library, i.e. &lt;code&gt;Math.min&lt;/code&gt;, &lt;code&gt;Math.max&lt;/code&gt; or something similar. In pseudocode, I&amp;rsquo;ll just call them &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; clamp(value, minimum, maximum) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; max(minimum, min(value, maximum))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Working inside out, it takes the smallest value, comparing &lt;code&gt;value&lt;/code&gt; and &lt;code&gt;maximum&lt;/code&gt;. This will result it &lt;code&gt;maximum&lt;/code&gt; at the most. It feeds that into the outer call to take the largest value between that and &lt;code&gt;minimum&lt;/code&gt;. It has the same effect of clamping it into that range.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s one upgrade that can make the function even more valuable. Allow for the &lt;code&gt;minimum&lt;/code&gt; and &lt;code&gt;maximum&lt;/code&gt; values to be reversed. If you&amp;rsquo;re calling the function directly, it&amp;rsquo;s easy enough to make sure the smaller of the two goes first. But if it&amp;rsquo;s part of a larger algorithm, it might happen that &lt;code&gt;minimum&lt;/code&gt; gets assigned a value that&amp;rsquo;s larger than &lt;code&gt;maximum&lt;/code&gt;. If so, you can just swap them and have it work fine. Here&amp;rsquo;s an easy take on this using a temp variable:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; clamp(value, minimum, maximum) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (minimum &lt;span style=&#34;color:#555&#34;&gt;&amp;gt;&lt;/span&gt; maximum) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        temp &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; minimum&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        minimum &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; maximum&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        maximum &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; temp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; max(minimum, min(value, maximum))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re lucky enough to be using a language that allows the following syntax, it makes it much easier&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; clamp(value, minimum, maximum) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (minimum &lt;span style=&#34;color:#555&#34;&gt;&amp;gt;&lt;/span&gt; maximum) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        minimum, maximum &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; maximum, minimum&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; max(minimum, min(value, maximum))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;examples&#34;&gt;Example(s)&lt;/h3&gt;&#xA;&lt;p&gt;One real life example where I had to use &lt;code&gt;clamp&lt;/code&gt; recently was in calculating color values. Normally rgb color channels can either go from 0.0 to 1.0 as floats, or from 0 to 255 as integers. In Cairographics, which I usually work with, it&amp;rsquo;s 0-1, and if you assign a value outside of that range, it doesn&amp;rsquo;t matter. I guess internally it clamps it to be within that range.&lt;/p&gt;&#xA;&lt;p&gt;But somewhat recently&amp;hellip; OK, a couple of years ago, I was creating a bitmap encoder. This stored an array of pixel channel values, which eventually got written to a file with the correct headers. Here&amp;rsquo;s a very simplified pseudocode version of the original function I came up with for setting the value of a single pixel:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; setPixel(x, y, r, g, b) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;index &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; imageWidth &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; x) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;pixels[index] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; b&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;pixels[index &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; g&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;pixels[index &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; r&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because this was a one dimensional array, I had to calculate the index of the array to store the red, green and blue values. They went in sequential elements in reverse order - blue, green, red. Each element was a float from 0.0 to 1.0.&lt;/p&gt;&#xA;&lt;p&gt;For the most part, this worked great. But now and then, I&amp;rsquo;d get a group of pixels that would just glitch out and render random, unpredictable colors - generally in very bright areas of the image. After a bit of debugging I realized that sometimes the calculated value of one or more channels would be greater than 1.0. It was either wrapping around or overflowing or something. At any rate, it looked like hell. Once I realized what the problem as, all I had to do was clamp the values to a range of 0 to 1.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; setPixel(x, y, r, g, b) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;index &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; (y &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; imageWidth &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; x) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;pixels[index] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; clamp(b, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;pixels[index &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; clamp(g, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;pixels[index &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; clamp(r, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Problem solved! If a channel value was too bright, it would just max out at 1.0. I don&amp;rsquo;t think I had an issue of values going negative, but the code would have guarded against that as well.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>OpenGL Steps: The Beginning</title>
      <link>https://bit-101.com/blog/posts/2024-01-27/opengl-steps-the-beginning/</link>
      <pubDate>Sat, 27 Jan 2024 21:35:20 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-27/opengl-steps-the-beginning/</guid>
      <description>&lt;p&gt;Just a link to a project I started. I&amp;rsquo;ve been working on learning OpenGL using &lt;a href=&#34;https://github.com/go-gl/gl&#34;&gt;go-gl&lt;/a&gt;. It&amp;rsquo;s a lot to take in, so I&amp;rsquo;ve been trying to break it down step by step, from the bare minimum to display a window, and then adding as little code as I can on each step - and understanding as much as I can about what&amp;rsquo;s going on in each step.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been working on preserving each step as a separate file as I go, and making each one executable. And in each one I comment all the major changes since the last step. This has been a great process. I&amp;rsquo;m up to step 7 now and learned a whole lot today. This will continue.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the link if you want to follow along:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/bit101/glsteps&#34;&gt;https://github.com/bit101/glsteps&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>FOTD: loopsin</title>
      <link>https://bit-101.com/blog/posts/2024-01-27/fotd-loopsin/</link>
      <pubDate>Sat, 27 Jan 2024 06:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-27/fotd-loopsin/</guid>
      <description>&lt;p&gt;This function, &lt;code&gt;loopsin&lt;/code&gt;, is one of my favorite functions ever, and one I am quite proud of. Though I&amp;rsquo;m sure I&amp;rsquo;m not the first to think of it, I came up with it on my own and have not seen it used or written about anywhere else. It&amp;rsquo;s not rocket science, but it&amp;rsquo;s immensely useful in animating perfect loops. I&amp;rsquo;ll also go a bit into the history of how I evolved this function over time.&lt;/p&gt;&#xA;&lt;h3 id=&#34;loopsin&#34;&gt;LoopSin&lt;/h3&gt;&#xA;&lt;h4 id=&#34;background&#34;&gt;Background&lt;/h4&gt;&#xA;&lt;p&gt;A popular (with me anyway) animation technique is to use a sine wave to oscillate some property of an object - make it go back and forth or up and down or get big and small, or whatever. You just feed the sine function an ever-increasing &lt;code&gt;t&lt;/code&gt; value, which gives you values that go back and forth between -1 and 1. Multiply that by some offset and add it to a center position, and you have oscillation.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// t initialized to 0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;offset &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;center &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;speed &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.01&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; center &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; sin(t) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; offset&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; speed&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sometimes you don&amp;rsquo;t really know what the center or offset are though. You just know you want this object to go back and forth between two values, say, 117 and 236. Well, our old friend &lt;code&gt;map&lt;/code&gt; is useful here.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// t initialized to 0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;speed &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.01&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;min &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;117&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;max &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;236&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;s &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sin(t)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(s, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, min, max)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; speed&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;evolving-the-function&#34;&gt;Evolving the function&lt;/h4&gt;&#xA;&lt;p&gt;When I started using Go for my creative coding, I moved away from interactive JavaScript pieces that ran forever and started creating animated gifs or videos that had a finite number of frames. One thing that really annoys me is gifs that run for a few seconds and then jump back to the start. It&amp;rsquo;s jarring and kills any positive aesthetic value the animation set out to create. So I wanted to create perfect loops.&lt;/p&gt;&#xA;&lt;p&gt;Well, I knew how many frames I had, and I knew the current frame. By dividing those two, I could get a &lt;code&gt;t&lt;/code&gt; value that went from 0 to &lt;em&gt;just under 1&lt;/em&gt; (see below). I could then map that to an angle from 0 to 2 * PI, and then run that through sine and map it to my range. This would form one full cycle through the range, which should then loop perfectly. So my function sort of looked like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; frame &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; frameCount&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;min &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;117&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;max &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;236&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; loopsin(t, min, max)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;circle(x, &lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; loopsin(t, min, max) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    a &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; PI&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    s &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sin(a)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; map(s, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, min, max)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here&amp;rsquo;s what that looks like, using &lt;code&gt;x&lt;/code&gt; as the position of a drawn circle:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;an oscillating circle&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-27/loopsin01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;A perfect loop!&lt;/p&gt;&#xA;&lt;p&gt;I used something almost just like this for years. But when I started doing animations that had multipe scenes, I had to improve this somewhat. To show the problem, I added a second scene to the above. I&amp;rsquo;m expecting the first scene to animate the circle from 117 to 236 and back. In the second scene, I want to keep the x position at 117, and animate the y position from 50 to 100 with this code:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// scene 2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; frame &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; frameCount&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;min &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;max &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;117&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; loopsin(t, min, max)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;circle(x, y, &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So again, I&amp;rsquo;m expecting it to go right, left, then down, up. But here&amp;rsquo;s what I get:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;an oscillating circle&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-27/loopsin02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Ouch.&lt;/p&gt;&#xA;&lt;p&gt;The problem is that I&amp;rsquo;m using sine, and mapping -1, 1 to min, max. The result of sine with an angle of zero will be zero. As the angle increases, it will go up to 1 and back to 0 then -1 and back to 0.&lt;/p&gt;&#xA;&lt;p&gt;So the animation will start and end at the midpoint of min and max. Meaning it will end at the midpoint of the ranges in scene 1 and jump to the midpoint of the ranges in scene 2. Aha!&lt;/p&gt;&#xA;&lt;p&gt;I can change that by using cosine rather than sine. The cosine of 0 is 1, it will then go down to 0 as the angle increases, then down more to -1 and back to 0 and 1. Better. But I&amp;rsquo;m mapping -1, 1 to min, max. This will have the animation start at max and animate to min. So I need to swap those so it goes from min to max instead. Here&amp;rsquo;s the new function:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; loopsin(t, min, max) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    a &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; PI&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    s &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; cos(a)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; map(s, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, min, max)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now maybe you&amp;rsquo;re thinking that I should change the function name to &lt;code&gt;loopcos&lt;/code&gt;. Whatever. Name it what you want. It&amp;rsquo;s still doing a sinusoidal curve of the values, so I&amp;rsquo;m not going to get hung up on it! :)&lt;/p&gt;&#xA;&lt;p&gt;Making that change, things now work as expected:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;an oscillating circle&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-27/loopsin03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Now that we&amp;rsquo;ve got it working just right, we can simplify it a bit&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; loopsin(t, min, max) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; map(cos(t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; PI), &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, min, max)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can apply this to more than just position. Here I&amp;rsquo;m using it to animate three different properties of objects:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;an oscillating circle&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-27/loopsin04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;In the first one, I&amp;rsquo;m calculating a radius with &lt;code&gt;loopsin&lt;/code&gt;. In the second, I&amp;rsquo;m using it to calculate a color value. And in the final one, a rotation. Perfect loops in every case. Rough pseudocode just to give you the idea:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; frame &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; frameCount&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; loopsin(t, &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;circle(&lt;span style=&#34;color:#f60&#34;&gt;80&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;75&lt;/span&gt;, r)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;v &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; loopsin(t, &lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1.0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rgb(v, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; v) &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// rgb values go from 0.0 to 1.0 here&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;circle(&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;75&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; loopsin(t, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;PI&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;, PI&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;translate(&lt;span style=&#34;color:#f60&#34;&gt;320&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;75&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rotate(a)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rect(&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;40&lt;/span&gt;, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;40&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;80&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;80&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you see an animation made by me, there&amp;rsquo;s a 90% chance that it uses this function.&lt;/p&gt;&#xA;&lt;h3 id=&#34;advanced-implementation-details&#34;&gt;Advanced implementation details&lt;/h3&gt;&#xA;&lt;p&gt;Earlier I said the by dividing the current frame number by total frames, will give us a number from zero to &lt;em&gt;just under one&lt;/em&gt;. This is because I zero-index my frames. If I have 60 frames, the frame numbers are 0 to 59. But I divide by the frame count, 60. So frame 0 gets a &lt;code&gt;t&lt;/code&gt; of 0.0. But frame 59 will get a &lt;code&gt;t&lt;/code&gt; of around 0.98333&amp;hellip;, which seems like a flaw. But in fact, it&amp;rsquo;s exactly what we want.&lt;/p&gt;&#xA;&lt;p&gt;When we convert &lt;code&gt;t&lt;/code&gt; to an angle, we&amp;rsquo;ll get angles from 0.0 to something just under 2 * PI. And when we pass that to the cosine function, the values we&amp;rsquo;ll get back will start at exactly 1.0, go down to -1.0 and back up to &lt;em&gt;just shy of 1.0&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;p&gt;When we map that to our min/max range, we&amp;rsquo;ll start with exactly min, move through to max and then back to &lt;em&gt;just shy of min&lt;/em&gt;. That&amp;rsquo;s perfect. Because on the next loop, it will start of at &lt;em&gt;exactly&lt;/em&gt; min again.&lt;/p&gt;&#xA;&lt;p&gt;If we &amp;ldquo;fixed&amp;rdquo; things so that &lt;code&gt;t&lt;/code&gt; went all the way to 1.0, then we&amp;rsquo;d start and end each loop at exactly min. Meaning we&amp;rsquo;d have one duplicate frame where it looped. You might not notice it, but in some cases, it could cause a tiny stutter.&lt;/p&gt;&#xA;&lt;p&gt;Alternately, you could have your frames start at 1, rather than 0. For 60 frames, this would mean the last frame would get a &lt;code&gt;t&lt;/code&gt; of 60/60 or 1.0, but the first frame would be 1/60, or slightly over 0.0. It should work either way, as long as you&amp;rsquo;re avoiding that duplicated frame. For me it makes more sense to start at 0.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Genuary2024, Days 19-25</title>
      <link>https://bit-101.com/blog/posts/2024-01-26/genuary-19-25/</link>
      <pubDate>Fri, 26 Jan 2024 18:54:09 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-26/genuary-19-25/</guid>
      <description>&lt;p&gt;Days 19 through 25 of &lt;a href=&#34;https://genuary.art&#34;&gt;genuary 2024&lt;/a&gt;!&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-19---flocking&#34;&gt;Day 19 - Flocking&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day19.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This isn&amp;rsquo;t really flocking. I explored flocking algorithms many years ago. I may have even written a book chapter about them at one time. They&amp;rsquo;re super interesting but I wasn&amp;rsquo;t super interested in diving into them at this point in time. So I did a little particle system. Experimented with scenes and how to make it loop smoothly. I just have the particles swooping in from the left, randomly wandering for a bit and then swooping out to the right. That leaves the canvas clear for the next swoop.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-20---generative-typography&#34;&gt;Day 20 - Generative typography&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day20.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This is an old technique I&amp;rsquo;ve been using for years, but I like that I was able to push it to a bit of a new level this time. Just drawing some lines across the screen. Test a hidden canvas that has something drawn to it. If you hit the drawing, you change the y position of the line. I tweened the height, and got some moving waves going in the lines. Kind of looks like an island rising out of the sea. Is it &amp;ldquo;generative typography&amp;rdquo;? I&amp;rsquo;d say no, but I like it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-21---use-a-library-that-you-havent-used-before&#34;&gt;Day 21 - Use a library that you haven’t used before&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day21.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Well, this is OpenGL. I&amp;rsquo;ve used it before, but it&amp;rsquo;s been many a year. Done some WebGL too. But really only scratched the surface of either. It&amp;rsquo;s been on my list to explore it a bit more again, so this was a good opportunity. There&amp;rsquo;s a &lt;code&gt;go-gl&lt;/code&gt; library for Go, that I was able to get running. Most people to the colored triangle as their GL &amp;ldquo;hello world&amp;rdquo;. I render a few thousand points. Anyway, I got a project template set up for OpenGL projects in Go, so once I&amp;rsquo;m done with the rest of this genuary melarky, I&amp;rsquo;ll be able to explore some more. I&amp;rsquo;ll also use this for Day 30, I think, which is Shaders.&lt;/p&gt;&#xA;&lt;p&gt;One thing I did here to create the animation was to export a bitmap after each frame render. I used a custom bitmap encoder I made a couple of years ago. Worked like a charm.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-22---point---line---plane&#34;&gt;Day 22 - Point - line - plane.&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day22.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I&amp;rsquo;m really not a fan of the &amp;ldquo;do something in the style of such and such artist&amp;rdquo; prompts. This one was pushing Kandinsky. Aight. I just did an animation of a bunch of points, then lines connecting them, then they turn into a plane, which turns into a point and starts all over again. A few cool techniques in this one though. I figured out how to do Poisson Disk Sampling, which is pretty neat. And added that to my library. Then I did my tried and true &amp;ldquo;connect-nearby-points-with-lines&amp;rdquo; technique. Followed by a convex hull algorithm, and an easing function. All tied together in a number of scenes.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-23---32x32&#34;&gt;Day 23 - 32x32&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day23.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;As a lot of people discovered, this prompt was purposely rigged to give different x-by-x numbers to different people. I got 32x32 on my computer, but 128x128 on my phone. I went with 32. I made a 32x32 grid of squares. In each one I drew concentric, rotated squares, a technique I&amp;rsquo;ve been playing with lately. They formed an interesting pattern at this scale. I timed them so that pattern formed an expanding ring.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-24---impossible-objects&#34;&gt;Day 24 - Impossible objects&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day24.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Subtitle, &amp;ldquo;undecided geometry&amp;rdquo;. I think a lot of people did isometric stuff for this one. Thank you Monument Valley. I tried to make this so that it would force your eye to see it differently when it rotated. I can see both ways each time though, but it does kind of shift around.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-25&#34;&gt;Day 25&lt;/h3&gt;&#xA;&lt;img alt=&#34;drops on a window&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/genuary2024/day25.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Long prompt for this one:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;If you like generative art, you probably have some photos on your phone of cool looking patterns, textures, shapes or things that you’ve seen. You might have even thought, “I should try to recreate this with code”. Today is the day.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I had been on a flight a little while back. Took some photos from the plane on the runway, trying to catch a sunrise. But it was rainy and I mostly just got a picture of raindrops on the window. Tried to recreate that. It came out&amp;hellip; OK, I guess.&lt;/p&gt;&#xA;&lt;p&gt;Six days to go!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>FOTD: Map</title>
      <link>https://bit-101.com/blog/posts/2024-01-26/fotd-map/</link>
      <pubDate>Fri, 26 Jan 2024 06:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-26/fotd-map/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve alluded to the power you get when you combine two or more useful functions. Today we&amp;rsquo;ll combine &lt;code&gt;norm&lt;/code&gt; and &lt;code&gt;lerp&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;map&#34;&gt;Map&lt;/h3&gt;&#xA;&lt;p&gt;The &lt;code&gt;map&lt;/code&gt; function has a bit of a learning curve because it has five parameters to understand, but it&amp;rsquo;s one of my most used functions. Once you really get it, you&amp;rsquo;ll probably use it all the time. Best to see it, then walk through it.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;func Map(srcValue, srcMin, srcMax, dstMin, dstMax) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; norm(srcValue, srcMin, srcMax)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; lerp(t, dstMin, dstMax)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Internally, it simply combines &lt;code&gt;norm&lt;/code&gt; and &lt;code&gt;lerp&lt;/code&gt;. But what a combo!&lt;/p&gt;&#xA;&lt;p&gt;The first three parameters are the same as the parameters to &lt;code&gt;norm&lt;/code&gt;. a value, minimum and maximum. In fact, those three parameters are fed straight into the &lt;code&gt;norm&lt;/code&gt; function, giving back a normalized value, &lt;code&gt;t&lt;/code&gt;. The first three parameters are the source value and range.&lt;/p&gt;&#xA;&lt;p&gt;Then, this &lt;code&gt;t&lt;/code&gt; value is used with the minumum and maximum values of the destination range.&lt;/p&gt;&#xA;&lt;h3 id=&#34;examples&#34;&gt;Example(s)&lt;/h3&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s walk through a simple one first.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(&lt;span style=&#34;color:#f60&#34;&gt;7&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, the function will normalize 7 in the range of 5 to 10. This is 0.4.&lt;/p&gt;&#xA;&lt;p&gt;Then it uses 0.4 as a &lt;code&gt;t&lt;/code&gt; value to lerp from 300 to 400. This gives us 340.&lt;/p&gt;&#xA;&lt;p&gt;Now a more useful example. I&amp;rsquo;ve generated an array of 100 random numbers called &lt;code&gt;nums&lt;/code&gt;. The way I&amp;rsquo;ve generated them, I don&amp;rsquo;t have any idea of the min and max values in the array. But I have a function that will go through and get these. Cool. Now I can loop through the list and get each number and map it to a value in the range of 0 to the height of the canvas, 300. This gives me the &lt;code&gt;y&lt;/code&gt; value.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;min &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; getMin(nums)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;max &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; getMax(nums)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt; nums.length; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    n &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; nums[i]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(n, min, max, &lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(i, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, nums.length, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lineto(x, y)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stroke()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the &lt;code&gt;x&lt;/code&gt; value, I can map the iterator, &lt;code&gt;i&lt;/code&gt; with a source range of 0 to the length of the array, and a destination range of 0 to the width of the canvas.&lt;/p&gt;&#xA;&lt;p&gt;Then I just draw a line to each new point.&lt;/p&gt;&#xA;&lt;p&gt;Note also that instead of using the destination range of 0, 300 in the map for y, I&amp;rsquo;m using 300, 0. The great thing about &lt;code&gt;norm&lt;/code&gt;, &lt;code&gt;lerp&lt;/code&gt; and &lt;code&gt;map&lt;/code&gt; is that you can flip the ranges without breaking anything. This lets the top of the canvas (zero) represent the maximum value and the bottom of the canvas (300) represent the minimum value, like you&amp;rsquo;d expect.&lt;/p&gt;&#xA;&lt;img alt=&#34;map example&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-26/map01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Without any math or knowledge of the values in the array, or even how long the array is, we walk away with a perfectly scaled graph of the values.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll note that the map for x is trivial:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(i, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, nums.length, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I could have just as easily said&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; nums.length &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But as before, this requires me to think about math in my head, whereas using the function I just think about the input range and output range. Also, if I decide to change the dimensions or position of the graph, like putting a border around it, I can do so easily by just changing the output ranges&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(n, min, max, &lt;span style=&#34;color:#f60&#34;&gt;280&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; map(i, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, nums.length, &lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;780&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;map example&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-26/map02.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;resources&#34;&gt;Resources&lt;/h3&gt;&#xA;&lt;p&gt;I did a video on this function on my &lt;a href=&#34;https://www.youtube.com/@codingmath&#34;&gt;Coding Math&lt;/a&gt; Youtube channel. View it here: &lt;a href=&#34;https://youtu.be/FxAEXHGZSPA?si=HviihMlqHKEVgBde&#34;&gt;Coding Math Mini #3, Map&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/coding-script-965345/&#34;&gt;Photo by Markus Spiske&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>FOTD: Lerp</title>
      <link>https://bit-101.com/blog/posts/2024-01-25/fotd-lerp/</link>
      <pubDate>Thu, 25 Jan 2024 04:47:06 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-25/fotd-lerp/</guid>
      <description>&lt;h3 id=&#34;lerp&#34;&gt;Lerp&lt;/h3&gt;&#xA;&lt;p&gt;The last Function Of The Day was &lt;code&gt;norm&lt;/code&gt;, which mapped a value in a range to a value between 0 and 1. The &lt;code&gt;lerp&lt;/code&gt; function does the exact opposite. It takes a normal value (something between 0.0 and &lt;code&gt;1.0&lt;/code&gt; and maps it into a given range. That is to say it interpolates between the minimum and maximum of that range, in a straight line - linearly. Linear Interpolation = lerp.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-code&#34;&gt;The Code&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; lerp(t, min, max) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; min &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; (max &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; min) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; t&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;t&lt;/code&gt; parameter (somewhat of a convention here) is the normal value, and &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; define the range.&lt;/p&gt;&#xA;&lt;p&gt;Generally, &lt;code&gt;t&lt;/code&gt; is considered to lie between 0 and 1. However, it can be smaller or larger. This will result in an output value that is outside of the range, which can actually be useful.&lt;/p&gt;&#xA;&lt;p&gt;Once again we get the size of the range by subtracting &lt;code&gt;min&lt;/code&gt; from &lt;code&gt;max&lt;/code&gt;. Multiplying that by &lt;code&gt;t&lt;/code&gt; puts you in the right place within that range. And adding that to &lt;code&gt;min&lt;/code&gt; shifts the range so it falls in the right place. If &lt;code&gt;t&lt;/code&gt; is zero, it will return &lt;code&gt;min&lt;/code&gt; and if &lt;code&gt;t&lt;/code&gt; is one, it returns &lt;code&gt;max&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;examples&#34;&gt;Example(s)&lt;/h3&gt;&#xA;&lt;p&gt;It&amp;rsquo;s often useful to think of the normal or &lt;code&gt;t&lt;/code&gt; value as a percentage. It can be useful inside a for loop. Say you want to draw a number of shapes across the width of a canvas. You can loop from 0 to 20 and divide the iteration value by 20 to get your &lt;code&gt;t&lt;/code&gt;. Then use that in &lt;code&gt;lerp&lt;/code&gt; to get an &lt;code&gt;x&lt;/code&gt; value and draw a shape at that point.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;20.0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; lerp(t, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    circle(x, &lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;lerp example&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-25/lerp01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now this is a trivial example because it would be just as easy to say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; t &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But say you wanted the dots to go from some different points. Just change the 0, 800 to something else.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; lerp(t, &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;700&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;lerp example&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-25/lerp02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;No additional math required.&lt;/p&gt;&#xA;&lt;p&gt;You could then &lt;code&gt;lerp&lt;/code&gt; &lt;code&gt;t&lt;/code&gt; to some other values like -PI to PI and then use that in a sine function.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; (i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#555&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;; i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    t &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;20.0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; lerp(t, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    a &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; lerp(t, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;PI, PI)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    y &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;150&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; sin(a) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    circle(x, y, &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img alt=&#34;lerp example&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-25/lerp03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The &lt;code&gt;lerp&lt;/code&gt; function is, all by itself, super useful, but becomes even more powerful when mixed with other useful functions. As we will see, eventually.&lt;/p&gt;&#xA;&lt;h3 id=&#34;resources&#34;&gt;Resources&lt;/h3&gt;&#xA;&lt;p&gt;I did a video on this function on my &lt;a href=&#34;https://www.youtube.com/@codingmath&#34;&gt;Coding Math&lt;/a&gt; Youtube channel. View it here: &lt;a href=&#34;https://youtu.be/mAi2-LTC2CA?si=ckJVDaOXKRNdkiSi&#34;&gt;Coding Math Mini #2, Linear Interpolation&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/coding-script-965345/&#34;&gt;Photo by Markus Spiske&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>FOTD: Norm</title>
      <link>https://bit-101.com/blog/posts/2024-01-24/fotd-norm/</link>
      <pubDate>Wed, 24 Jan 2024 05:06:07 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-24/fotd-norm/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m going to do a series of short articles, labeled &lt;code&gt;FOTD&lt;/code&gt; - Function Of The Day. These might be math functions, drawing functions, PRNG functions, color functions, etc. Mostly these will be useful for visual or creative coders.&lt;/p&gt;&#xA;&lt;p&gt;Each post will be short. Just a single function, how it works, what it might be used for, and maybe an example or two. No particular schedule for these. As the mood strikes me. But between the two main libraries I&amp;rsquo;ve curated, &lt;code&gt;blcairo&lt;/code&gt; and &lt;code&gt;bitlib&lt;/code&gt;, I have close to 1000 functions, so there should be no shortage of material. Maybe worth noting that I&amp;rsquo;ll usually be using some kind of pseudocode for all the examples.&lt;/p&gt;&#xA;&lt;h3 id=&#34;norm&#34;&gt;Norm&lt;/h3&gt;&#xA;&lt;p&gt;Today&amp;rsquo;s function is &lt;code&gt;norm&lt;/code&gt;. This takes in three parameters: &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;. It returns a number between 0.0 and 1.0 based on where &lt;code&gt;value&lt;/code&gt; falls between &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;. Actually, if &lt;code&gt;value&lt;/code&gt; is less than &lt;code&gt;min&lt;/code&gt; it will return a negative number, and if &lt;code&gt;value&lt;/code&gt; is greater than &lt;code&gt;max&lt;/code&gt; it will return something larger than 1.0. But in most use cases, the min/max range will be contrived so that &lt;code&gt;value&lt;/code&gt; fits into it cleanly.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-code&#34;&gt;The Code&lt;/h3&gt;&#xA;&lt;p&gt;In pseudocode, &lt;code&gt;norm&lt;/code&gt; looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; norm(value, min, max) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; (value &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; min) &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; (max &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; min)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;max - min&lt;/code&gt; part gets the range between those two values. Subtracting &lt;code&gt;min&lt;/code&gt; from &lt;code&gt;value&lt;/code&gt; puts value into that range (if it can be put into that range). Dividing the two give you zero if &lt;code&gt;value&lt;/code&gt; equals &lt;code&gt;min&lt;/code&gt;, one if &lt;code&gt;value&lt;/code&gt; equals &lt;code&gt;max&lt;/code&gt; and somewhere between if &lt;code&gt;value&lt;/code&gt; is between the two.&lt;/p&gt;&#xA;&lt;h3 id=&#34;examples&#34;&gt;Example(s)&lt;/h3&gt;&#xA;&lt;p&gt;There&amp;rsquo;s one use for &lt;code&gt;norm&lt;/code&gt; that&amp;rsquo;s immediately useful. I often use Perlin or Simplex noise to generate patterns or what have you. In some libraries these noise functions return a value between 0.0 and 1.0, which is already normalized. But in most, they return a value from -1.0 to 1.0. I have color functions that take values for channels in floats between 0.0 and 1.0, so I need to convert between the two.&lt;/p&gt;&#xA;&lt;p&gt;I could say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;colorValue &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; (noise &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// or...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;colorValue &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; noise &lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Adding one moves the noise range to 0.0 to 2.0. Dividing by two puts it into a normalized range. The second version does something similar.&lt;/p&gt;&#xA;&lt;p&gt;With &lt;code&gt;norm&lt;/code&gt; I can say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;colorValue &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; norm(noise, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s a couple more characters, but for me, less of a cognitive load. There&amp;rsquo;s no math involved, I&amp;rsquo;m just mapping from -1, 1 to 0, 1.&lt;/p&gt;&#xA;&lt;p&gt;Norm is often used as just one step in a calculation. Once you have the norm, you might want to do some further calculations with that value.&lt;/p&gt;&#xA;&lt;p&gt;Say I was using &lt;code&gt;atan2&lt;/code&gt; to calculate angles between two points, like a center point and another pointer. This gives you a number from -PI to PI. You can use &lt;code&gt;norm&lt;/code&gt; to normalize that to a 0, 1 range. Multiply that value by the width of the canvas, in my case 400, and now you can use that to draw a kind of gauge showing the value. Like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;angle &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; atan2(y &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; centerY, x &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; centerX)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;value &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; norm(angle, &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;PI, PI) &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;moveto(value, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lineto(value, &lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;animation of norm in use&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-24/norm01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Now this might seem a little bit contrived, because honestly it is. The fact is, I don&amp;rsquo;t use &lt;code&gt;norm&lt;/code&gt; by its own all the much other than the noise example above. But it goes well inside other more complex functions, some of which we&amp;rsquo;ll visit later.&lt;/p&gt;&#xA;&lt;h3 id=&#34;resources&#34;&gt;Resources&lt;/h3&gt;&#xA;&lt;p&gt;I did a video on this function on my &lt;a href=&#34;https://www.youtube.com/@codingmath&#34;&gt;Coding Math&lt;/a&gt; Youtube channel. View it here: &lt;a href=&#34;https://www.youtube.com/watch?v=hJqRcExiBk0&#34;&gt;Coding Math Mini #1, Normalization&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/coding-script-965345/&#34;&gt;Photo by Markus Spiske&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Books</title>
      <link>https://bit-101.com/blog/posts/2024-01-23/books/</link>
      <pubDate>Tue, 23 Jan 2024 05:00:55 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-23/books/</guid>
      <description>&lt;p&gt;For a good part of my tech career, writing has gone hand in hand with coding. Early on, I was writing tutorials and have had very active blog for many years. I even wrote several magazine articles. But writing books was a big part of what I did too. I found myself looking at some of my old books recently and wanted to do a recap. Most of these are based on Flash and versions of ActionScript. This might explain why my output has slowed in recent years.&lt;/p&gt;&#xA;&lt;p&gt;The following list is complete (I think), but very definitely NOT in chronological order. More in stream of consciousness order.&lt;/p&gt;&#xA;&lt;h4 id=&#34;flash-math-creativity&#34;&gt;Flash Math Creativity&lt;/h4&gt;&#xA;&lt;img alt=&#34;Flash Math Creativity&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/fmc1.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This is the one that started it all. 2001. I had written a few Flash tutorials that had gotten a lot of attention. I was contacted by Friends of ED, a prolific publisher of Flash and web related books. They asked me if I wanted to write a chapter for this book they were starting to work on. Each chapter would be some creative, math-based idea, with lots of imagery, included code, and a write up of the inspiration and mechanics behind the piece. I said yes of course. And that started everything.&lt;/p&gt;&#xA;&lt;h4 id=&#34;flash-math-creativity-second-edition&#34;&gt;Flash Math Creativity, Second Edition&lt;/h4&gt;&#xA;&lt;img alt=&#34;Flash Math Creativity, Second Edition&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/fmc2.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This is not the next book I worked on, but I&amp;rsquo;ll put it next because it&amp;rsquo;s a follow up to the original. It&amp;rsquo;s actually basically the same book. The original was written for ActionScript, later to be called &amp;ldquo;ActionScript 1.0&amp;rdquo;. The second edition was rewritten for ActionScript 2.0. I did the entire rewrite. I converted all the code to AS2 and then went through all the text and updated the technical descriptions to reflect those changes. It was a bit strange rewriting the chapters that these other great authors (many of whom I looked up to and admired) had written. I limited myself to just changing the technical descriptions only and touching as little of the rest of the chapters as I could.&lt;/p&gt;&#xA;&lt;h4 id=&#34;the-most-wanted-series&#34;&gt;The Most Wanted Series&lt;/h4&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Flash MX Most Wanted Effects and Movies&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/flashmostwanted.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;Flash MX 2004 Games Most Wanted&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/gamesmostwanted.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Flash MX Most Wanted Effects and Movies&lt;/em&gt; and &lt;em&gt;Flash MX 2004 Games Most Wanted&lt;/em&gt; were part of a series of books Friends of ED put out where they got a bunch of authors to write a chapter or more on related subjects. To be honest, I&amp;rsquo;m not sure where the &amp;ldquo;most wanted&amp;rdquo; part came in. It was a long time ago, but as I recall they just asked me to submit some ideas, which I did, and wound up writing those chapters. I think I did two or three chapters each for these two books. I don&amp;rsquo;t remember what the other ones in the series were, but I&amp;rsquo;m quite sure there were more.&lt;/p&gt;&#xA;&lt;h4 id=&#34;other-chapter-books&#34;&gt;Other Chapter Books&lt;/h4&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Byte Sized Flash&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/bytesizedflash.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;Flash MX Studio&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/flashmxstudio.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;Fresh Flash&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/freshflash.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Friends of ED loved the whole chapter book idea. And I guess it worked for them. These were a few other books I did anywhere from one to three chapters for.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Bite-Size Flash MX, Adventures in Design Optimization&lt;/em&gt; was early &amp;ldquo;size coding&amp;rdquo; I guess you&amp;rsquo;d say. I used have PHPBB forum on my site and each month I&amp;rsquo;d sponsor a &amp;ldquo;25 lines&amp;rdquo; contest. The idea was to write the coolest possible Flash piece in 25 lines or less of ActionScript. It was freaking amazing what people did. Of course eventually it got out of hand and people were really bending the concept of what 25 lines was and it got caught up in rules and became less fun. Anyway, this was along those lines. 4k or less.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Flash MX Studio&lt;/em&gt; was I guess some kind of professionally targeted book on how to do cool stuff in Flash. I don&amp;rsquo;t remember much more about it at this point.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Fresh Flash, New Design Ideas with Flash MX&lt;/em&gt; was um&amp;hellip; who knows? New design ideas with Flash MX I guess. I wrote some chapters for it apparently. I couldn&amp;rsquo;t tell you what they were about.&lt;/p&gt;&#xA;&lt;h4 id=&#34;extending-flash-mx-2004&#34;&gt;Extending Flash MX 2004&lt;/h4&gt;&#xA;&lt;img alt=&#34;Extending Flash MX 2004&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/extendingflash.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Flash MX 2004 was an IDE that included an extensibility layer. You could write various extensions for it - plugins, basically. It was all in a language called JSFL - JavaScript Flash. You could create your own UIs within the IDE, create new drawing tools that let the user draw custom shapes, script actions that automated the whole IDE, etc. It didn&amp;rsquo;t get used all that much, which was sad because you could do some pretty amazing things with it. I created an extension that let you play Asteroids right in the IDE. And another one that gave you a drawing tool for creating 3D objects. Another Flash developer I knew at the time, Todd Yard, was one of the few other people who was into this stuff as well. So we pitched this book and wrote it. It was very cool, but hardly anybody ever used JSFL ever. And I think they removed the whole layer from the next version, Flash 8.&lt;/p&gt;&#xA;&lt;h4 id=&#34;new-masters-of-flash-volume-3&#34;&gt;New Masters of Flash, Volume 3&lt;/h4&gt;&#xA;&lt;img alt=&#34;New Masters of Flash, Volume 3&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/nmof.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Being invited to write a chapter for New Masters was, for me, at that time, the best thing that had ever happened to me. It was like winning gold at the Olympics. I had the previous two versions of this book and had poured over them for years. The people in those books were my heros, my role models, eventually many became good friends too. So when I got into volume 3 it was huge. I wrote a chapter on particle systems, which was an obsession I had at that time. And lots of background on myself and my journey as a creative coder. I re-read it recently, prepared to cringe. But it was actually not bad. I enjoyed it.&lt;/p&gt;&#xA;&lt;h4 id=&#34;making-things-move-the-quadrilogy&#34;&gt;Making Things Move! The Quadrilogy&lt;/h4&gt;&#xA;&lt;img alt=&#34;ActionScript Animation, Making Things Move&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/asanimation.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;For some years I&amp;rsquo;d been writing tutorials, writing books, doing daily experiments, seeking out cool new techniques for coding all kinds of visual, interactive, physics- and math-based Flash stuff. I wanted to put this all in a single book. I knew it would be a great book. I pitched it to Friends of ED (by then shut down and reincarnated by Apress). They were not interested. I pitched it to a couple of other publishers. No deal. I pitched it a couple more times to Friends of ED and finally I got a YES!&lt;/p&gt;&#xA;&lt;p&gt;This was the first book I wrote cover-to-cover by myself. It took months. It was an amazing process. It was the culmination of years of daily obsession with Flash, vomited into a single volume. It&amp;rsquo;s the professional accomplishment I am the most proud of.&lt;/p&gt;&#xA;&lt;p&gt;Here are the opening paragraphs:&lt;/p&gt;&#xA;&lt;img alt=&#34;AS Animation Intro&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/intro.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Though Flash is now very officially dead, I still keep a copy of this book on my shelf and regularly find myself looking up techniques I wrote about nearly 20 years ago. Others have told me the same. The techniques and formulas are timeless.&lt;/p&gt;&#xA;&lt;p&gt;The book went over quite well. For a while it was the best selling book on the ActionScript language. It sold a lot of copies. The title &amp;ldquo;Making Things Move!&amp;rdquo; was my initial idea. The publisher wanted to put the book in its &amp;ldquo;Foundation&amp;rdquo; series, so the title became &amp;ldquo;Foundation ActionScript Animation&amp;rdquo;, with &amp;ldquo;Making Things Move!&amp;rdquo; a subtitle. But it&amp;rsquo;s always been &amp;ldquo;Making Things Move!&amp;rdquo; to me.&lt;/p&gt;&#xA;&lt;p&gt;Funny story, one of the initial cover images they came up for the book was a group of zombies. Get it? Making things move? Luckily, they iterated a bit on that one and came up with something quite nice.&lt;/p&gt;&#xA;&lt;p&gt;Like &lt;em&gt;Flash Math Creativity&lt;/em&gt;, it was written for an earlier version of ActionScript, so eventually I had to write an AS3 version.&lt;/p&gt;&#xA;&lt;img alt=&#34;AS3 Animation&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/as3animation.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;By then I&amp;rsquo;d learned and thought of some more stuff I wish I&amp;rsquo;d put in the book. I kept a list of subjects and when it became long enough, I wrote a sequel, &lt;em&gt;AdvancED ActionScript Animation&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;img alt=&#34;Advanced AS3 Animation&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/advancedas3animation.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This was just more cool techniques. It fit in well with the original.&lt;/p&gt;&#xA;&lt;p&gt;Some time later, when Flash was breathing its last breaths, I was asked to convert Making Things Move to JavaScript. I said no. I regret saying no, but it is what it is. They got another author, Billy Lamberta, to do the conversion. I endorsed it, got my name on the cover, and even got royalties for it. But I should have written it. Oh well. Billy did a great job.&lt;/p&gt;&#xA;&lt;img alt=&#34;HTML5 Animation&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/html5animation.jpg&#34; /&gt;&#xA;&#xA;&lt;h4 id=&#34;actionscript-30-cookbook&#34;&gt;ActionScript 3.0 Cookbook&lt;/h4&gt;&#xA;&lt;img alt=&#34;ActionScript 3.0 Cookbook&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/as3cookbook.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This was the first book I worked on that wasn&amp;rsquo;t for Friends of ED / Apress. I wrote it with a couple of friends, Joey Lott and Darren Schall. We each wrote roughly a third of it, as I recall. Maybe Joey wrote more. It was cool I guess. I&amp;rsquo;m not a huge fan of the cookbook format. It was less fun to write than a less structured project.&lt;/p&gt;&#xA;&lt;h4 id=&#34;iphone-user-interface-design-projects&#34;&gt;iPhone User Interface Design Projects&lt;/h4&gt;&#xA;&lt;img alt=&#34;iPhone User Interface Design Projects&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/iphoneui.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This was also for Apress, but not part of the Friends of ED line. I was doing iPhone development for a while and got asked to write a chapter (maybe more than one?) for this book. I don&amp;rsquo;t remember what I wrote about. I also spoke at some iOS conference around that time (not WWDC or anything, just a smaller conference put on by some people I knew). I thought iOS dev was going to be my next big thing. I had a number one game on the early App Store - Falling Balls. But I got fed up with how Apple controlled everything and was already becoming developer hostile. I got an Android phone, moved to Linux for a while and that was that.&lt;/p&gt;&#xA;&lt;h4 id=&#34;playing-with-chaos&#34;&gt;Playing With Chaos&lt;/h4&gt;&#xA;&lt;img alt=&#34;Playing With Chaos&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-23/playingwithchaos.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;This was the last actual book I wrote, more than ten years ago. And it was the first book I self-published. I&amp;rsquo;d done a talk by the same name in Cologne, Germany at the Beyond Tellerand conference, hosted by my good friend, Marc Thiele. It was all about coding Mandelbrot and Julia sets, strange attractors, IFS fractals, cellular automata, etc. It was very well received and I thought it would make a good book. I sat down and wrote it and published it on Amazon Kindle. I also published it on &lt;a href=&#34;https://bit101.gumroad.com/l/playingwithchaos&#34;&gt;Gumroad&lt;/a&gt;. It still sells copies and people say pretty good things about it. So that&amp;rsquo;s nice.&lt;/p&gt;&#xA;&lt;h4 id=&#34;coding-curves&#34;&gt;Coding Curves&lt;/h4&gt;&#xA;&lt;p&gt;This is not really a book, but it might be some day. I had this as an idea as a follow up to &lt;em&gt;Playing With Chaos&lt;/em&gt; and it was going to be called &lt;em&gt;Playing With Curves&lt;/em&gt;. I started writing this book every year or two. And I would generally start from scratch each time and get a chapter or two before giving up.&lt;/p&gt;&#xA;&lt;p&gt;In 2022, I decided to do it as a series of online chapters. Here it is: &lt;a href=&#34;https://www.bit-101.com/2017/2022/11/coding-curves/&#34;&gt;Coding Curves&lt;/a&gt;. It wound up 14 chapters long, written and published one by one, from November 2022 to February 2023. I&amp;rsquo;m glad I finally got this done and doing it as a series of blog posts was a good technique. If I hadn&amp;rsquo;t done it that way, it would probably still be something I planned to do someday.&lt;/p&gt;&#xA;&lt;p&gt;As I said, I might eventually gather all the chapters together and publish it as a single book. Maybe even for free at this point. Another project on the pile.&lt;/p&gt;&#xA;&lt;h4 id=&#34;coding-colors&#34;&gt;Coding Colors&lt;/h4&gt;&#xA;&lt;p&gt;This is a future project. It will be along the same lines as Coding Curves. I have a table of contents, but there&amp;rsquo;s a lot to figure out on how to do this one, to make it language/platform agnostic, since different languages and graphics APIs represent color very differently. I&amp;rsquo;ll get there someday.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Making Some Points</title>
      <link>https://bit-101.com/blog/posts/2024-01-20/making-some-points/</link>
      <pubDate>Sat, 20 Jan 2024 15:00:46 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-20/making-some-points/</guid>
      <description>&lt;h3 id=&#34;some-go-basics&#34;&gt;Some Go Basics&lt;/h3&gt;&#xA;&lt;p&gt;A lot of what I do is with two-dimensional points. As covered earlier, I&amp;rsquo;m doing all my work in Go, so I have a &lt;code&gt;Point&lt;/code&gt; struct that looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Point&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;struct&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;X,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And a construction method that looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;NewPoint&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&lt;/span&gt;Point{X:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Y:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In Go, you can them make &amp;ldquo;methods&amp;rdquo; for the Point struct that look like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// Angle returns the angle from the origin to this point.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;Angle&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Atan2&lt;/span&gt;(p.Y,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p.X)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;(p *Point)&lt;/code&gt; part just marks this as a method that can be called with any point as a &amp;ldquo;receiver&amp;rdquo;. So I can say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;p0&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.&lt;span style=&#34;color:#c0f&#34;&gt;NewPoint&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dist&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p0.&lt;span style=&#34;color:#c0f&#34;&gt;Angle&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is all in the &lt;code&gt;geom&lt;/code&gt; package, so any time I&amp;rsquo;m calling any of this outside the package, I prefix it with the package name.&lt;/p&gt;&#xA;&lt;p&gt;Quite often I&amp;rsquo;m dealing with a whole bunch of points. So I can put them in an array (aka a &amp;ldquo;slice&amp;rdquo; in Go). A point array would look like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;[]&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;geom.Point{}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// add some points to this list:&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;append&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.&lt;span style=&#34;color:#c0f&#34;&gt;NewPoint&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;))&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;append&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.&lt;span style=&#34;color:#c0f&#34;&gt;NewPoint&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;))&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;append&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.&lt;span style=&#34;color:#c0f&#34;&gt;NewPoint&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;))&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fmt.&lt;span style=&#34;color:#c0f&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#366&#34;&gt;len&lt;/span&gt;(points))&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// 3&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can also make a random point within a rectangle:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;append&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.&lt;span style=&#34;color:#c0f&#34;&gt;RandomPointInRect&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height))&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In my actual drawing library I have a method that will draw a list of points like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Points&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The second arg is the radius that each point will be drawn with. I made it large here so you can see it:&lt;/p&gt;&#xA;&lt;img alt=&#34;random points&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-20/points_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;There&amp;rsquo;s also a method to draw a path with a point list:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;StrokePath&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;false&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The boolean parameter sets whether or not the path should be closed. i.e. draw a line back to the first point.&lt;/p&gt;&#xA;&lt;p&gt;And the result:&lt;/p&gt;&#xA;&lt;img alt=&#34;random points&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-20/points_02.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;moving-on-from-there&#34;&gt;Moving on from there&amp;hellip;&lt;/h3&gt;&#xA;&lt;p&gt;All this is pretty basic stuff. But recently I enhanced this by making a new &lt;code&gt;PointList&lt;/code&gt; type. This is just an alias to an array of points. But once you make it its own specific type, a lot of opportunities appear. Here&amp;rsquo;s the type definition and one of the construction methods.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// PointList is a slice of Points&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;PointList&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;[]&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// NewPointList creates a new point list&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;NewPointList&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;PointList&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;PointList{}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Typing &lt;code&gt;geom.NewPointList()&lt;/code&gt; &lt;em&gt;feels&lt;/em&gt;  lot better than typing &lt;code&gt;[]*geom.Point{}&lt;/code&gt; even though it&amp;rsquo;s a few characters longer. At least it feels better to me, and it works better with auto-complete.&lt;/p&gt;&#xA;&lt;p&gt;And now I can attach methods to this new type, like for adding new points:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// Add adds a point to the list&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;PointList)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;Add&lt;/span&gt;(point&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;Point)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;point)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// AddXY adds a point to the list&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;PointList)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;AddXY&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#366&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;NewPoint&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y))&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now maybe I can do something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.&lt;span style=&#34;color:#c0f&#34;&gt;NewPointList&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;x&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;x&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;x&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;y&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.&lt;span style=&#34;color:#c0f&#34;&gt;Sin&lt;/span&gt;(x&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.01&lt;/span&gt;)&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;300&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;AddXY&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// here, I&amp;#39;ll point AND stroke&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Points&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;StrokePath&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;false&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Typing &lt;code&gt;points.AddXY(x, y)&lt;/code&gt; &lt;em&gt;definitely&lt;/em&gt; feels better than &lt;code&gt;points = append(points, geom.NewPoint(x, y))&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;This gets us this:&lt;/p&gt;&#xA;&lt;img alt=&#34;random points&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-20/points_03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now points have other methods on them, many of which you surely expect. Methods for translating, scaling and rotating and some other fun stuff. If I want to, say, rotate all the points by some small amount, I can loop through the point list and rotate each one like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;_,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;p.&lt;span style=&#34;color:#c0f&#34;&gt;Rotate&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// then draw as before...&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the whole path is rotated from the origin.&lt;/p&gt;&#xA;&lt;img alt=&#34;random points&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-20/points_04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;If I want to rotate the path from some specific point instead of the 0, 0, origin, I can used &lt;code&gt;RotateFrom&lt;/code&gt; on each point:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;_,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;p.&lt;span style=&#34;color:#c0f&#34;&gt;RotateFrom&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Pi&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This rotates everything 90 degrees around the center for the image:&lt;/p&gt;&#xA;&lt;img alt=&#34;random points&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-20/points_05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I have point methods for &lt;code&gt;Translate&lt;/code&gt;, &lt;code&gt;Rotate&lt;/code&gt;, &lt;code&gt;RotateFrom&lt;/code&gt;, &lt;code&gt;Scale&lt;/code&gt;, &lt;code&gt;ScaleFrom&lt;/code&gt;, as well as things like &lt;code&gt;Randomize&lt;/code&gt; and &lt;code&gt;Noisify&lt;/code&gt;. And I used them fairly often. After a few thousand times recreating the above for-loop, I realized I could just add methods to &lt;code&gt;PointList&lt;/code&gt; that do all that for me. So now &lt;code&gt;PointList&lt;/code&gt; has all these same methods. They work exacly how I&amp;rsquo;d been doing it manually. Here&amp;rsquo;s an example:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// ScaleFrom scales all the points in a list using the x, y location as a center.&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;PointList)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;ScaleFrom&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;sx,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;sy&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;_,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;point&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;range&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;p&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;point.&lt;span style=&#34;color:#c0f&#34;&gt;ScaleFrom&lt;/span&gt;(x,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;y,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;sx,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;sy)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, in the above example, in order to rotate the whole list, I can just say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;RotateFrom&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Pi&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s a demo of all of the above. Ten scenes showing: translate, scale, scale from, rotate, rotate from, randomize, and a few manipulations of noisify.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-20/points_06.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;That&amp;rsquo;s about it. Just an example of iterating a code base to make complex things much easier. If you&amp;rsquo;re interested in the code itself it&amp;rsquo;s at my &lt;a href=&#34;https://github.com/bit101/bitlib&#34;&gt;bitlib repo&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;In the off chance that you&amp;rsquo;re interested in the entire code for this whole last demo, here it is:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// Package main renders an image, gif or video&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;package&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;main&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;import&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/bitlib/blmath&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/bitlib/easing&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/bitlib/geom&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/bitlib/random&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;cairo&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/blcairo&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/blcairo/render&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/blcairo/target&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;main&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;renderTarget&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;target.Video&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;switch&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;renderTarget&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;case&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;target.Image:&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;Image&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/out.png&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;scene1,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;ViewImage&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/out.png&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;break&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;case&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;target.Video:&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;NewProgram&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;30&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene1,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene2,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene3,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene4,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene5,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene6,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene7,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene8,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene9,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene10,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;RenderVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/frames&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;PlayVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;break&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.PointList&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;Seed&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.&lt;span style=&#34;color:#c0f&#34;&gt;PoissonDiskSampling&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;3&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;30&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;geom.PointList)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;BlackOnWhite&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Points&lt;/span&gt;(points,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene1&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Translate&lt;/span&gt;(t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene2&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Scale&lt;/span&gt;(t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene3&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;ScaleFrom&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene4&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Rotate&lt;/span&gt;(blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;))&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene5&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;RotateFrom&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;))&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene6&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Randomize&lt;/span&gt;(t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene7&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;easing.&lt;span style=&#34;color:#c0f&#34;&gt;LinearEase&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;6&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Noisify&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0.005&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.005&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene8&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.5&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Noisify&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0.005&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.005&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;6&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene9&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.005&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.001&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Noisify&lt;/span&gt;(t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;6&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene10&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;setup&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;t&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;easing.&lt;span style=&#34;color:#c0f&#34;&gt;LinearEase&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;6&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;points.&lt;span style=&#34;color:#c0f&#34;&gt;Noisify&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0.005&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.005&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;t)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;draw&lt;/span&gt;(context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;points)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each &amp;ldquo;scene&amp;rdquo; just creates the point list, alters it in some way, and draws it. The alteration is just a couple lines of code. Pretty neat.&lt;/p&gt;&#xA;&lt;p&gt;Don&amp;rsquo;t judge the code too harshly, it&amp;rsquo;s just a demo I threw together for testing these methods.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/timelapse-photography-of-stars-at-night-169789/&#34;&gt;Photo by InstaWalli from Pexels:&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Genuary2024, Days 13-18</title>
      <link>https://bit-101.com/blog/posts/2024-01-18/genuary2024-days-13-18/</link>
      <pubDate>Thu, 18 Jan 2024 06:00:38 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-18/genuary2024-days-13-18/</guid>
      <description>&lt;p&gt;The next six days of &lt;a href=&#34;https://genuary.art&#34;&gt;genuary 2024&lt;/a&gt;!&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-13---wobbly-function-day&#34;&gt;Day 13 - Wobbly function day&lt;/h3&gt;&#xA;&lt;p&gt;Might have to start this one manually. Set it to loop and turn up the sound a bit.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;controls&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day13plus.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I didn&amp;rsquo;t quite follow the spirit of the prompt, but the strings do wobble. I wanted to practice synchronizing video and sound. Tones created in Supercollider with a synthdef I made last year that sounds sort of like strings.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-14---less-than-1kb-artwork&#34;&gt;Day 14 - Less than 1kb artwork&lt;/h3&gt;&#xA;&lt;img alt=&#34;small mandelbrot&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/genuary2024/day14.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Didn&amp;rsquo;t want to do size coding. Wanted to see the best Mandelbrot image I could make in 1K. Had to JPG the heck out of it after it rendered.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-15---use-a-physics-library&#34;&gt;Day 15 - Use a physics library&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day15.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Not quite a physics library, but it&amp;rsquo;s my easing library that I wrote last year and haven&amp;rsquo;t really used. It works pretty well. Been using it more since this, so that&amp;rsquo;s cool.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-16---draw-10000-of-something&#34;&gt;Day 16 - Draw 10,000 of something&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day16.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;10,000 strange attractors rendered with 10,000 points on a 10,000 pixel canvas. Frame rate is 1 fps so the movie takes 10,000 seconds (around 2 hours 46 minutes).&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a 60 fps version that takes 2 minutes and 46 seconds.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day16fast.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h3 id=&#34;day-17---inspired-by-islamic-art&#34;&gt;Day 17 - Inspired by Islamic art&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day17.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Not Islamic art. You&amp;rsquo;ll have to take my word that it was &amp;ldquo;inspired by&amp;rdquo;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-18---bauhaus&#34;&gt;Day 18 - Bauhaus&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day18.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Call back to a business card I once had from a company I worked for.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Creative Challenges</title>
      <link>https://bit-101.com/blog/pages/creative-challenges/</link>
      <pubDate>Sun, 14 Jan 2024 09:20:17 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/pages/creative-challenges/</guid>
      <description>&lt;p&gt;There are many community-based creative projects that people can participate in. Some of these are competitions with strict rules. Others are just prompts to inspire you over a given period of time. Some are for coding, others for music, writing, drawing or &amp;hellip; whatever.&lt;/p&gt;&#xA;&lt;p&gt;As for the links, I  tried to find some that were the most informative if not completely &amp;ldquo;official&amp;rdquo; in every case. I&amp;rsquo;m happy to update any of the below info or make additions.&lt;/p&gt;&#xA;&lt;div class=&#34;table&#34;&gt;&#xA;  &lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Time&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Name&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Theme&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Hashtag&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Link&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;January&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Genuary&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;generative art&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#genuary&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;genuary.art&#34;&gt;genuary.art&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;January&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Jamuary&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;music&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#jamuary&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://www.jonathanmann.net/jamuary&#34;&gt;jonathanmann.net&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Feb 9-11&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Lovebyte Party&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;size coding&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#lovebyte&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://lovebyte.party/&#34;&gt;lovebyte.party&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;March-April&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Watt-Wise Game Jam&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;energy efficient gaming&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#wattwisegamejam&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://wattwise.games/&#34;&gt;wattwise.games&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;May&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;May for Makers&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;misc&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#mayformakers&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://www.plsmithed.com/mayformakers&#34;&gt;plsmithed.com&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;October&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Mathtober&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;math&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#mathtober&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://fractalkitty.com/2022/09/17/mathober-2022-prompts/&#34;&gt;fractalkitty.com&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;October&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Inktober&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;drawing (ink)&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#inktober&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://inktober.com/&#34;&gt;inktober.com&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;November&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;NaNoWriMo&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;writing&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#nanowrimo&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://nanowrimo.org/&#34;&gt;nanowrimo.org&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;November&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Codevember&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;games or general code&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#codevember&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://codevember.org/&#34;&gt;codevember.org/&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;December&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;December Adventure&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;anything!&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#decemberadventure&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://eli.li/december-adventure&#34;&gt;eli.li&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Weekly&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Disquiet Junto&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Music&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;#disquietjunto&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a href=&#34;https://disquiet.com/tag/junto/&#34;&gt;disquiet.com&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/people-gathered-watching-a-panda-mascot-2346289/&#34;&gt;Header photo by Jeffrey Czum&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>My Creative Workflow - Libraries and Frameworks</title>
      <link>https://bit-101.com/blog/posts/2024-01-12/my-creative-workflow-libraries-and-frameworks/</link>
      <pubDate>Fri, 12 Jan 2024 17:17:05 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-12/my-creative-workflow-libraries-and-frameworks/</guid>
      <description>&lt;p&gt;In this article, I want to talk about using libraries to make creative coding faster, easier and more enjoyable.&lt;/p&gt;&#xA;&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;&#xA;&lt;p&gt;For more years that I&amp;rsquo;d like to admit, I engaged in reinventing wheels. I&amp;rsquo;d re-write the same code over and over. If I wanted to find the distance between two points, instead of using a distance function, I&amp;rsquo;d just write (pseudocode):&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dx &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; p0.x &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; p1.x&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dy &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; p0.y &lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt; p1.y &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dist &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; sqrt(dx &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; dx &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; dy &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; dy)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Another example: most drawing APIs supply you with an &lt;code&gt;arc&lt;/code&gt; function that&amp;rsquo;s something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;arc(x, y, radius, startAngle, endAngle, counterclockwise)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So every time I wanted to draw a circle, I&amp;rsquo;d type it all out rather than creating a circle function:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;arc(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;, PI &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;false&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I could probably come up with dozens of other examples. If I wanted to draw a path through an array of points, I&amp;rsquo;d loop through the array and draw a line to each successive point, when I should have created a path function. I&amp;rsquo;d done all these things so many times that I didn&amp;rsquo;t even have to think about them, the code would just fall out of my fingertips. So one of the plus points of this approach was that I learned a lot of concepts really, really well.&lt;/p&gt;&#xA;&lt;p&gt;And sometimes I wanted to do something slightly different - it was easy enough to change the code right there because it wasn&amp;rsquo;t hidden away in a function.&lt;/p&gt;&#xA;&lt;p&gt;Or maybe in addition to needing to know the distance between two points, I&amp;rsquo;d also need to know the angle between them. In that case, I could re-use the intermediate &lt;code&gt;dx&lt;/code&gt; and &lt;code&gt;dy&lt;/code&gt; variables in the &lt;code&gt;atan2&lt;/code&gt; function, building it a bit of optimization. Calling a distance function and then an angle function would calculate these values twice internally.&lt;/p&gt;&#xA;&lt;p&gt;Another plus point was that I never needed any dependencies. In a call back to a &lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-07/my-creative-workflow-templates/&#34;&gt;previous article&lt;/a&gt;, I could just open an editor and start typing, without needing to pull in a library or framework.&lt;/p&gt;&#xA;&lt;p&gt;But as time went on and projects got more complex, I had to come with the grips with the fact that I was wasting a lot of time writing the same code over and over. And sometimes I&amp;rsquo;d figure out some complex idea, but it wasn&amp;rsquo;t something I&amp;rsquo;d use very often. Then later I&amp;rsquo;d need to do that thing again and I&amp;rsquo;d either have to figure it out again, or dig up the project where I&amp;rsquo;d done it before.&lt;/p&gt;&#xA;&lt;p&gt;So I created a library that I called &lt;code&gt;bitlib&lt;/code&gt; and started dumping all my functions in there. I started this in ActionScript and started it again, probably multiple times in JavaScript. I still wasn&amp;rsquo;t a total convert to this workflow though. I&amp;rsquo;d use the library sometimes, but not others. It was never very well organized and I kept recreating a new version rather than trying to improve an existing one. I think it was some time in 2016 that I got a bit more serious about curating this library - in JavaScript at the time. Purely for reference: &lt;a href=&#34;https://github.com/bit101/bitlibjs&#34;&gt;https://github.com/bit101/bitlibjs&lt;/a&gt; - this is not something I use anymore.&lt;/p&gt;&#xA;&lt;p&gt;In 2017 I consolidated this library even more while working on a new version of the &lt;a href=&#34;https://bit101.github.io/lab/&#34;&gt;BIT-101 Lab&lt;/a&gt;. In the &lt;a href=&#34;https://github.com/bit101/lab&#34;&gt;source code&lt;/a&gt; for that project, you can see multiple versions of &lt;code&gt;bitlib&lt;/code&gt; in the &lt;code&gt;libs&lt;/code&gt; folder. As I did new experiments, I was adding new functions to the lib. This was good. Right direction.&lt;/p&gt;&#xA;&lt;h3 id=&#34;rust-and-go&#34;&gt;Rust and Go&lt;/h3&gt;&#xA;&lt;p&gt;&lt;a href=&#34;http://bit-101.com/blog/posts/2024-01-09/my-creative-workflow-language-and-platform/&#34;&gt;As mentioned&lt;/a&gt;, in late 2017 I started exploring Rust. On January 1, 2018 I blogged about a port of &lt;code&gt;bitlib&lt;/code&gt; to Rust - &lt;code&gt;bitlib-rs&lt;/code&gt;. &lt;a href=&#34;https://www.bit-101.com/2017/2018/01/introducing-bitlib-rs-for-rust-and-cairo/&#34;&gt;https://www.bit-101.com/2017/2018/01/introducing-bitlib-rs-for-rust-and-cairo/&lt;/a&gt;. This was designed to work with the Cairographics bindings I was using to generate images and animations in Rust. Most of this was a direct port of what I had in JavaScript at the time.&lt;/p&gt;&#xA;&lt;p&gt;A short while later, when I switched over to Go, I ported it all to that language. There&amp;rsquo;s been a few iterations and reorganizations of the code, but it&amp;rsquo;s followed a very direct, highly curated lineage for the past six years and I&amp;rsquo;m very proud of it. And that&amp;rsquo;s what I want to spend the rest of this article talking about.&lt;/p&gt;&#xA;&lt;p&gt;My library currently consists of two main code bases.&lt;/p&gt;&#xA;&lt;h3 id=&#34;blcairo&#34;&gt;BLCAIRO&lt;/h3&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/bit101/blcairo&#34;&gt;blcairo&lt;/a&gt; is the main graphics library. It creates an image surface and a drawing context and beyond the Cairographics drawing API, it has over 100 custom drawing functions added to the context. So to draw a circle, I don&amp;rsquo;t have to use &lt;code&gt;Arc&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Arc&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;math.Pi,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;false&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can just say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Circle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each drawing function also has stroke and fill versions, so rather than saying:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Circle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Stroke&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can just say:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;StrokeCircle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These were trivial to code, but have saved me countless hours - not only in typing the extra line, but in trying to figure out why nothing got drawn because I &lt;em&gt;forgot&lt;/em&gt; to type the extra line.&lt;/p&gt;&#xA;&lt;p&gt;In addition to the core drawing API and extensions, there&amp;rsquo;s a &lt;code&gt;render&lt;/code&gt; package that has all kinds of stuff in it for creating projects that generate animations, sprite sheets, videos, animated gifs, montages, etc. from multiple images. I&amp;rsquo;ll take a deeper dive into that shortly.&lt;/p&gt;&#xA;&lt;h3 id=&#34;bitlib&#34;&gt;BITLIB&lt;/h3&gt;&#xA;&lt;p&gt;The other key part of my core libraries is the Go version of &lt;code&gt;bitlib&lt;/code&gt;. This consists of all kinds of utilities that do not directly have to do with rendering (at least via &lt;code&gt;blcairo&lt;/code&gt;). To avoid circular dependencies, I make sure that &lt;code&gt;blcairo&lt;/code&gt; can depend on &lt;code&gt;bitlib&lt;/code&gt;, but not the other way around. The packages in &lt;code&gt;bitlib&lt;/code&gt; have, over the years, grown to the following:&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;bitmap&lt;/strong&gt; is an alternate rendering target. &lt;code&gt;blcairo&lt;/code&gt; is great for drawing vector shapes such as lines, curves, rectangles and paths, etc. But sometimes I need to iterate through every pixel of an image and set its color. The Cairo context is not great for that. I&amp;rsquo;ve often just drawn a 1x1 rectangle for each pixel, but that seems like overkill. You can get the image data from the surface and do the math to set each element of that data to the specific red, green, blue and alpha values and then put the image data back into the surface and then save it. That also seems like overkill. So I wrote my own bitmap encoder which works on an array of RGB color values. It&amp;rsquo;s very lightweight and pretty fast. So for doing something like Mandelbrot or other fractals, it&amp;rsquo;s ideal.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;blcolor&lt;/strong&gt; is a color library that integrates wonderfully with &lt;code&gt;blcairo&lt;/code&gt;. You can create RGB or RGBA color values with integers (0-255) for each channel, or floats (0.0-1.0) per channel, a single integer (0x000000-0xFFFFFF for RGB, but also an alpha version), HSV, HSL, CMYK, or grayscale. You can create random colors or interpolate between two colors to create another color. There are a dozen and a half methods to compare colors (equals, straight diff, perceptual diff), evaluate certain aspects of colors (luminance, contrast), convert from RGB to HSV, HSL, CMYK, grayscale) or alter colors (scale, quantize, add, rotate, invert). There are methods for almost 150 standard named colors (Turquoise, Alice Blue, Olive, etc.), and a package for creating, manipulating and using color palettes.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;blmath&lt;/strong&gt; contains a lot of useful math functions beyond what exists in the Go &lt;code&gt;math&lt;/code&gt; module. A few dozen functions and constants, always growing. The ones I use the most are those that map a number within a certain range to another range, or clamp it to that range, wrap it around that range, map it exponentially, etc. There&amp;rsquo;s one that I use constantly that I&amp;rsquo;ll cover later.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;easing&lt;/strong&gt; is an implementation of Robert Penner&amp;rsquo;s easing formulas. This lets you say I want the value of this variable to move from x to y over a certain time period or number of frames. It might start out fast and ease into position at some rate, or ease out of its original position slowly, or do both. Or bounce or spring around a certain point, etc. These exact equations are used in many animation libraries. One thing I&amp;rsquo;d like to add is some sequencing, so I could set a chain of different motions into effect.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;geom&lt;/strong&gt; has all kinds of geometry based functions and types. Points, lines, segments, rectangles, triangles, paths, circles, etc. Ways to create these objects and manipulate them. There&amp;rsquo;s also some collision detection routines in there. It&amp;rsquo;s not fully fleshed out, but you can see if most of these types are colliding with the same type of some of the other types, and often get the point(s) of collision.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;noise&lt;/strong&gt; contains Perlin and Simplex noise functions - in one, two and three dimensions.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;random&lt;/strong&gt; is a PRNG (pseudo random number generator). This uses Go&amp;rsquo;s built-in &lt;code&gt;rand&lt;/code&gt; module under the hood, but creates an much improved and more useful (IMO) API on top of it. You can get random floats or ints, or a float or an int within a certain range, or an array of floats or ints. You can get a random boolean or a weighted boolean. There are methods that use normal distributions or powers. You can also generate a random string of a given length with given types of characters. You can generate random numbers directly through the module, i.e. &lt;code&gt;x = random.Float()&lt;/code&gt; which is fine most of the time, but if you&amp;rsquo;re running multiple routines (threads) they will share the same PRNG, which can mess you up. In that case you can create a random number generator for each routine and use that safely:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rng&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;random.&lt;span style=&#34;color:#c0f&#34;&gt;NewRandom&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;=&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;rng.&lt;span style=&#34;color:#c0f&#34;&gt;Float&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;the-render-package&#34;&gt;The Render Package&lt;/h3&gt;&#xA;&lt;h4 id=&#34;rendering-images&#34;&gt;Rendering Images&lt;/h4&gt;&#xA;&lt;p&gt;Moving back to &lt;code&gt;blcairo&lt;/code&gt;, I want to talk about the &lt;code&gt;render&lt;/code&gt; package because I&amp;rsquo;m pretty proud of some of the stuff in there. None of the stuff in this package is required to use to create images or animations, but it makes things far easier. Without this package, it&amp;rsquo;s still pretty easy to create a still image. It looks something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;main&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;surface&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;cairo.&lt;span style=&#34;color:#c0f&#34;&gt;NewSurface&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;cairo.&lt;span style=&#34;color:#c0f&#34;&gt;NewContext&lt;/span&gt;(surface)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;ClearRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;     &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// green&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;SetSourceRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// red&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;FillRectangle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;surface.&lt;span style=&#34;color:#c0f&#34;&gt;WriteToPNG&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.png&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you run that, it creates the following image:&lt;/p&gt;&#xA;&lt;img alt=&#34;red square on green background&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-12/out_01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Using the &lt;code&gt;render&lt;/code&gt; package, you can change that to:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;main&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;Image&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.png&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;renderFrame,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;ViewImage&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.png&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;renderFrame&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;ClearRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;     &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// green&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;SetSourceRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// red&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;FillRectangle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, you can just call the &lt;code&gt;render.Image&lt;/code&gt; function, passing in the size of the image, a name for the output, a function that has the actual rendering code and a percent value. Forget about that last param for a moment. Now the surface and context are created for you and your &lt;code&gt;renderFrame&lt;/code&gt; function is called with the needed params. You do the drawing and that&amp;rsquo;s it. There&amp;rsquo;s even a function that calls an installed image viewr to view the final image immediately.&lt;/p&gt;&#xA;&lt;p&gt;Now that might not seem like all that much of an improvement. Same number of lines of code, and we&amp;rsquo;ve added an extra function. But it becomes pretty freaking awesome when you move into animation.&lt;/p&gt;&#xA;&lt;h4 id=&#34;rendering-animations&#34;&gt;Rendering Animations&lt;/h4&gt;&#xA;&lt;p&gt;Say you want to do a two-second animation at 30 frames per second. You have to render 60 separate images to disk (or maybe memory). And you have to figure out how to change each image on each frame to create the animation that you want. Then you have to take all of those generated frames and assemble them into an animated gif or a video or a spritesheet or whatever.&lt;/p&gt;&#xA;&lt;p&gt;That can be dozens of lines of complicated code: math, file io, calling external tools.&lt;/p&gt;&#xA;&lt;p&gt;Look how simple this becomes:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;main&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;Frames&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;./frames&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;renderFrame)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;ConvertToVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;./frames&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.mp4&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;30&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;PlayVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;renderFrame&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;ClearRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;     &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// green&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;SetSourceRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// red&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;FillRectangle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;percent)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run this, and within a second, you see this video playing:&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-12/out_01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;First we call &lt;code&gt;render.Frames&lt;/code&gt; which takes a width and height, the total number of frames to render (2 seconds times 30 fps), a directory to store the frame images and a function to call to render each frame. The frames will be created as files named &lt;code&gt;frame_0000.png&lt;/code&gt; through &lt;code&gt;frame_0059.png&lt;/code&gt; in this case.&lt;/p&gt;&#xA;&lt;p&gt;Then we call &lt;code&gt;render.ConvertToVideo&lt;/code&gt;. This takes the frames directory, the output file name, the size, fps and total time of the video in seconds. Yes, there&amp;rsquo;s some redundancy here, but hold that thought.&lt;/p&gt;&#xA;&lt;p&gt;Finally, there&amp;rsquo;s a function that you can configure to play the video with your video player of choice.&lt;/p&gt;&#xA;&lt;p&gt;Note that the signature of the &lt;code&gt;renderFrame&lt;/code&gt; function has not changed. So we could continue to use this with &lt;code&gt;render.Image&lt;/code&gt;. In practice, I often switch back and forth between the two, as I&amp;rsquo;ll mention later.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, &lt;code&gt;renderFrame&lt;/code&gt; now makes use of the final &lt;code&gt;percent&lt;/code&gt; parameter. Each of the 60 times that &lt;code&gt;renderFrame&lt;/code&gt; is called, percent will be slightly bigger, going from 0.0 the first time it&amp;rsquo;s called (first frame) up to just under 1.0 for the final frame. Here, I&amp;rsquo;m using the &lt;code&gt;percent&lt;/code&gt; value to change the size of the square I&amp;rsquo;m drawing. Pretty cool.&lt;/p&gt;&#xA;&lt;h4 id=&#34;perfect-loops&#34;&gt;Perfect Loops!&lt;/h4&gt;&#xA;&lt;p&gt;But there&amp;rsquo;s a problem with this animation. It does not cleanly loop. The square expands and then jumps back to its original size. I like my animations to loop perfectly. So we go back to the &lt;code&gt;blmath&lt;/code&gt; package and one of my favorite functions, &lt;code&gt;blmath.LerpSin&lt;/code&gt;. This is a combination of &amp;ldquo;lerp&amp;rdquo; for &amp;ldquo;linear interpolation&amp;rdquo; and Sin as in a Sine function. It&amp;rsquo;s a bad name because it&amp;rsquo;s not linear at all, but I&amp;rsquo;m sticking with it. What it does is map the 0.0 to 1.0 of &lt;code&gt;percent&lt;/code&gt; to a new range of values that will loop from a minimum to a maximum using a sine wave. So you&amp;rsquo;d do something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;renderFrame&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;ClearRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;     &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// green&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;SetSourceRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// red&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;size&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;FillRectangle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;size,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;size)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, we&amp;rsquo;re mapping percent onto values going from 0 to 200. We save this in a &lt;code&gt;size&lt;/code&gt; variable and use that to draw the square.&lt;/p&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-12/out_02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Less than a dozen lines of code and we have a perfectly looping animation.&lt;/p&gt;&#xA;&lt;h4 id=&#34;making-it-even-better&#34;&gt;Making it even better!&lt;/h4&gt;&#xA;&lt;p&gt;As mentioned, there&amp;rsquo;s still some excess wordiness and duplication of parameters in there. A more recent update solves a lot of this as well. We can refactor the &lt;code&gt;main&lt;/code&gt; function into:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;main&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;program&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;NewProgram&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;30&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithSeconds&lt;/span&gt;(scene1,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;RenderVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;./frames&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;PlayVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This introduces the concept of a &amp;ldquo;program&amp;rdquo; which contains a number of &amp;ldquo;scenes&amp;rdquo;. We create a new program with a size and fps.&lt;/p&gt;&#xA;&lt;p&gt;Then we add a single scene, giving it a render method (which I renamed to &lt;code&gt;scene1&lt;/code&gt;) and a number of seconds. We could also use &lt;code&gt;program.AddSceneWithFrames&lt;/code&gt; to specify the length of the scene in frames rather than seconds.&lt;/p&gt;&#xA;&lt;p&gt;Finally we just tell the program to render a video, giving it a place to save the frames and a name for the output file. Much simpler.&lt;/p&gt;&#xA;&lt;p&gt;And now, as you can probably guess, we can easily build up complex animations using multiple scenes. Here&amp;rsquo;s an example, with fewer than 20 lines of code for a two-scene animation.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;main&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;program&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;NewProgram&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;30&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithSeconds&lt;/span&gt;(scene1,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithSeconds&lt;/span&gt;(scene2,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;RenderVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;./frames&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;PlayVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene1&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;ClearRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;     &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// green&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;SetSourceRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// red&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;size&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;FillRectangle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;size,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;size)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene2&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;ClearRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;     &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// green&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;SetSourceRGB&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// red&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;size&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;100&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;FillCircle&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;200&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;size)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-12/out_03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h3 id=&#34;bringing-it-all-together&#34;&gt;Bringing it all together&lt;/h3&gt;&#xA;&lt;p&gt;A lot of the stuff I&amp;rsquo;ve talked about in the last few posts is now coming together. I&amp;rsquo;ve shown you the language and platform I&amp;rsquo;m using and the libraries and frameworks I&amp;rsquo;ve created. You&amp;rsquo;ve seen the enhanced Cairo drawing routines, the &lt;code&gt;blmath&lt;/code&gt; stuff, and the power of the &lt;code&gt;render&lt;/code&gt; package. Now let&amp;rsquo;s bring it full circle and show you the template that I use to start a new project:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-Go&#34; data-lang=&#34;Go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;package&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;main&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;import&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/bitlib/blmath&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;cairo&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/blcairo&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/blcairo/render&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;github.com/bit101/blcairo/target&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;main&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;renderTarget&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;target.Gif&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;switch&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;renderTarget&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;case&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;target.Image:&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;Image&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;800&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/out.png&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;scene1,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0.0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;ViewImage&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/out.png&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;break&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;case&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;target.Video:&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;NewProgram&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;400&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;30&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;AddSceneWithFrames&lt;/span&gt;(scene1,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;60&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;program.&lt;span style=&#34;color:#c0f&#34;&gt;RenderVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/frames&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;render.&lt;span style=&#34;color:#c0f&#34;&gt;PlayVideo&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;out/out.mp4&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;break&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;func&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c0f&#34;&gt;scene1&lt;/span&gt;(context&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;cairo.Context,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;height,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;percent&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;float64&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;{&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;BlackOnWhite&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Save&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;TranslateCenter&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;DrawAxes&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0.25&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;r&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;blmath.&lt;span style=&#34;color:#c0f&#34;&gt;LerpSin&lt;/span&gt;(percent,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;50&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;width&lt;span style=&#34;color:#555&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;sphere&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;cairo.&lt;span style=&#34;color:#c0f&#34;&gt;NewSphere&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;r,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;sphere.&lt;span style=&#34;color:#c0f&#34;&gt;SetShadowColor&lt;/span&gt;(&lt;span style=&#34;color:#f60&#34;&gt;0.2&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;sphere.&lt;span style=&#34;color:#c0f&#34;&gt;Draw&lt;/span&gt;(context)&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#x9;&lt;/span&gt;context.&lt;span style=&#34;color:#c0f&#34;&gt;Restore&lt;/span&gt;()&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It seems like a lot, but it&amp;rsquo;s mainly stuff we&amp;rsquo;ve already seen. At the start of &lt;code&gt;main&lt;/code&gt; I just create a &lt;code&gt;renderTarget&lt;/code&gt; variable. It starts out with the value &lt;code&gt;target.Video&lt;/code&gt;. But it could also be &lt;code&gt;target.Image&lt;/code&gt;, or &lt;code&gt;target.Gif&lt;/code&gt;, &lt;code&gt;target.SpriteSheet&lt;/code&gt;, &lt;code&gt;target.Montage&lt;/code&gt;. Then it will call one of the blocks there, which either renders an image or video. If you want to render one of the other targets you can add a block for that. I can easily change that render target to change what it renders without changing any other code.&lt;/p&gt;&#xA;&lt;p&gt;Sometimes I&amp;rsquo;m working on a video piece which takes several seconds to render a whole bunch of frames. But I&amp;rsquo;m just trying to get a basic form looking right. So I&amp;rsquo;ll switch over to rendering it as an image, which is much faster and allows me to iterate a lot quicker. Then when I have it down, I jump back to rendering the video.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;scene1&lt;/code&gt; function just draws an animated red sphere - I showed that in a previous post. Usually when I create a new project using my &lt;a href=&#34;https://www.npmjs.com/package/tinpig&#34;&gt;tinpig&lt;/a&gt; tool, I&amp;rsquo;ll make the animation just to ensure it&amp;rsquo;s working correctly. Then I&amp;rsquo;ll clear out most of the &lt;code&gt;scene1&lt;/code&gt; code and start doing what I want.&lt;/p&gt;&#xA;&lt;p&gt;One other bit of bling in the &lt;code&gt;render&lt;/code&gt; package is when you go to render a video it gives you nice feedback about what&amp;rsquo;s going on. During render, you get a progress bar and an estimated time of completion:&lt;/p&gt;&#xA;&lt;img alt=&#34;rendering progress bar&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-12/rendering.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;(I upped the number of frames to 6000 to give myself time to do the screenshot!)&lt;/p&gt;&#xA;&lt;p&gt;And when it&amp;rsquo;s done, it gives you a bunch of information about what it did.&lt;/p&gt;&#xA;&lt;img alt=&#34;rendering progress bar&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-12/rendered.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;It might sound like I&amp;rsquo;m giving you a sales pitch here. I&amp;rsquo;m honestly not. I don&amp;rsquo;t know if a single other person in the world is using these libraries. And that&amp;rsquo;s 100% fine with me. I made them for me and I love using them. In fact, if a lot of people used these, I&amp;rsquo;d have to be more worried about maintenance, avoiding breaking changes, dealing with feature requests, etc. So I&amp;rsquo;m happy being the main (or only) user.&lt;/p&gt;&#xA;&lt;p&gt;My point is more that you should iterate on your own workflow until it makes you as happy as my workflow makes me.&lt;/p&gt;&#xA;&lt;p&gt;It might also sound like I&amp;rsquo;m bragging a bit here. And sure, I definitely am. I&amp;rsquo;m pretty proud of what I&amp;rsquo;ve created here. It&amp;rsquo;s the culmination of many years of tiny improvements though. And culmination isn&amp;rsquo;t the best word either because I&amp;rsquo;m 100% sure that a year from now it will be even better.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.flickr.com/photos/lungstruck/3224745399&#34;&gt;Header Image &amp;#39;Old books in Sarah&amp;#39;s house&amp;#39; by lungstruck is licensed under CC BY 2.0&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Genuary 2024, Days 7-12</title>
      <link>https://bit-101.com/blog/posts/2024-01-12/genuary-7-12/</link>
      <pubDate>Fri, 12 Jan 2024 05:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-12/genuary-7-12/</guid>
      <description>&lt;p&gt;Another six days of &lt;a href=&#34;https://genuary.art&#34;&gt;genuary 2024&lt;/a&gt; are done! Here they are, with some random ramblings.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-7---progress-bar&#34;&gt;Day 7 - Progress Bar&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day07.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I was thinking of a progress bar that was not just a line filling up from left to right, or a circle or arc doing the same. Went through a lot of iterations of this. Ended up on this progression from a simple boring left-to-right progress bar, to total chaos. It still gets the idea across though.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-8---chaotic-system&#34;&gt;Day 8 - Chaotic System&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day08.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This is a pretty run-of-the-mill 2d strange attractor, but I just kind of made up the formula on my own. I usually have to look up a formula, but I just made some params and threw some sines and cosines at it, and tested some random values until I saw some nice attractors coming out of it and honed in on that range. Beyond that, nothing too special here.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-9---ascii&#34;&gt;Day 9 - ASCII&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day09.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Same formula as Day 8, but far fewer points, and I labeled each one and drew lines between successive generated points. Interesting effect I thought.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-10---hexagonal&#34;&gt;Day 10 - Hexagonal&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day10.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This started as something similar I&amp;rsquo;d done with a rectangular grid a while back - rotated and scaled shapes inside of each cell. But I added an offset to it as well which gives it an almost 3d feel.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-11---in-the-style-of-anni-albers-1899-1994&#34;&gt;Day 11 - In the style of Anni Albers (1899-1994)&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day11.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I was not familiar with Anni Albers. I looked over her work and was intrigued by a fabric piece called &lt;a href=&#34;https://www.theguardian.com/artanddesign/2019/jan/18/anni-albers-intersecting&#34;&gt;Intersecting&lt;/a&gt;. The basic concept here is borrowed from that. Each line occasionally jumps lanes and uses a dampened spring formula to settle into that lane.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-12---lava-lamp&#34;&gt;Day 12 - Lava Lamp&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day12.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;This grew out of a much simpler, much more boring piece I almost published as is. It was just multiple flat morphing blob shapes in a grid. But I realized I could do something similar to what I did with the hexagons in Day 10. Then added some colors and just tweened the hell out of every conceivable property.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>My Creative Workflow - Language and Platform</title>
      <link>https://bit-101.com/blog/posts/2024-01-09/my-creative-workflow-language-and-platform/</link>
      <pubDate>Tue, 09 Jan 2024 14:08:08 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-09/my-creative-workflow-language-and-platform/</guid>
      <description>&lt;p&gt;The language and platform you use for creative coding is a huge deciding factor in how you will work, how much you will enjoy working, and even the kinds of work you will do. So it&amp;rsquo;s worth shopping around and coming up with something that lets you do the kind of things you want to do and makes it a good experience.&lt;/p&gt;&#xA;&lt;h3 id=&#34;beginnings---flash-and-actionscript&#34;&gt;Beginnings - Flash and ActionScript&lt;/h3&gt;&#xA;&lt;img alt=&#34;flash logo&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-09/flash.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;As readers of this blog will likely know, I started out my career in Flash. I&amp;rsquo;d wanted to do creative coding as far back as the mid-80s and had made some games and creative apps in BASIC on the Commodore Amiga, but never got beyond that until I discovered Flash around 1998. The workflow there just clicked for me and I was able to do all kinds of stuff that I only dreamed about before that. No, that&amp;rsquo;s not right. I was able to do far more than I had dreamed about.&lt;/p&gt;&#xA;&lt;p&gt;What I loved about Flash was that it was initially a combination of coding and more traditional drawing and animation tools. You could draw something with the mouse or a tablet and animate it on the timeline, or you could animate it with code. Later versions of ActionScript gave us an actual drawing API where we could create shapes in code, and then animate them in code. At that point, for many of us, the Flash IDE became unnecessary and we moved on to code-centric tools like MTASC (Motion Twin ActionScript Compiler) and various code editors. Later there was FlexBuilder and a standalone SDK for creating ActionScript projects, and third party dev environments like FDT (Flash Development Tool).&lt;/p&gt;&#xA;&lt;p&gt;What I liked about ActionScript was how easy it was to create art and animation. The drawing API was decent and a first class part of the tool. As was the display list and animation system. The &lt;code&gt;enterFrame&lt;/code&gt; event let us code the timeline. The display list with nested sprites and movie clips made complex animation a piece of cake. Initially the language was loosley typed, but even when typing and classes were added, it remained very expressive. It never tripped you up with fussy syntax (though it did have its idiosyncracies). And compilation was pretty fast. Nearly instantaneous for simple things, and not bad for even more complex projects. The Flash Player was ubiquitous, so you could upload your content and the whole world could see it and interact with it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;processing&#34;&gt;Processing&lt;/h3&gt;&#xA;&lt;img alt=&#34;processing logo&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-09/processing.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;When Flash&amp;rsquo;s days were numbered, &lt;a href=&#34;https://processing.org&#34;&gt;Processing&lt;/a&gt; (or, as it was known a the time, &amp;ldquo;Proce55ing&amp;rdquo;) was an obvious replacement for many. It had a lot going for it, even back then. A very simple IDE, but you could also code using your own editor and tools. A super rich drawing an animation API, well beyond what Flash/ActionScript had. One drawback for me was that Processing was Java-based. And the output for the web was Java applets. These were not nearly as accessible as Flash movies. And by &amp;ldquo;not accessible&amp;rdquo; I simply mean that people could not access them because they didn&amp;rsquo;t have the Java plugin installed in their browser. But it&amp;rsquo;s great for installations or for generating movies, animations or graphics that would be used elsewhere.&lt;/p&gt;&#xA;&lt;p&gt;Later, the JavaScript adaptation of Processing - &lt;a href=&#34;https://p5js.org&#34;&gt;p5.js&lt;/a&gt; brought much of the core functionality of Processing back to the web.&lt;/p&gt;&#xA;&lt;p&gt;I used Processing off and on but it never made it to the top of my list of languages and platforms. The same with p5.js. I have a lot of respect for both. I just never went deep with them.&lt;/p&gt;&#xA;&lt;p&gt;Both versions of this tool are very solid choices to this day for creative coders.&lt;/p&gt;&#xA;&lt;h3 id=&#34;cinder&#34;&gt;Cinder&lt;/h3&gt;&#xA;&lt;img alt=&#34;cinder logo&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-09/cinder.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://libcinder.org/&#34;&gt;Cinder&lt;/a&gt; was another platform that saw a lot of adoption by creative coders around the time of Flash&amp;rsquo;s demise. It was co-created by &lt;a href=&#34;https://roberthodgin.com/&#34;&gt;Robert Hodgin&lt;/a&gt;, a hugely influential artist in the Flash community at the time, initially known by his project, flight404, one of the original classic Flash experimental sites.&lt;/p&gt;&#xA;&lt;p&gt;Cinder is C++ based, and was far more powerful than most other tools in use at the time, and came with a higher learning curve. It was, and probably still is, used for many professional projects.&lt;/p&gt;&#xA;&lt;h3 id=&#34;unity3d&#34;&gt;Unity3D&lt;/h3&gt;&#xA;&lt;img alt=&#34;unity logo&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-09/unity.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://unity.com/&#34;&gt;Unity&lt;/a&gt; was another obvious choice for a lot of Flash developers looking for a new home. It was mainly targeted as a 3D tool, but eventually had capable 2D animation workflows as well. It had a serious IDE for creating visual content and animation as well as coding in C# and JavaScript (if I recall correctly). Towards the end of Flash&amp;rsquo;s life, 3D was a popular subject on the platform. There were multiple home grown 3D libraries like Papervision3D and Away3D, and eventually Adobe added native 3D to the tool. For the people who were into that, the switch to Unity was a no-brainer. And it remains a hugely popular platform for game development in particular.&lt;/p&gt;&#xA;&lt;h3 id=&#34;javascript&#34;&gt;JavaScript&lt;/h3&gt;&#xA;&lt;img alt=&#34;javascript logo&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-09/javascript.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Of course, for many of us, JavaScript was an obvious path. It was mostly the same language as ActionScript - based on ECMAScript - and with the release of HTML5&amp;rsquo;s Canvas and 2D drawing context, it had a very capable drawing api which was very similar to Flash&amp;rsquo;s. The &lt;code&gt;requestAnimationFrame&lt;/code&gt; function was essentially the same as ActionScript&amp;rsquo;s &lt;code&gt;enterFrame&lt;/code&gt; event. So transitioning from Flash to JS/HTML/Canvas was quite easy. Additionally, it had the same benefit of allowing you to upload your creation and having it be available to the world. This is the path I took.&lt;/p&gt;&#xA;&lt;p&gt;As a side note, Canvas also eventually supported a 3D context using WebGL, so it supported native 3D as well.&lt;/p&gt;&#xA;&lt;p&gt;Anyway, when I was starting out on this path in March 2011, I did a series of 30 blog posts - one per day - about my journey in learning JS. &lt;a href=&#34;https://www.bit-101.com/2003/?p=3030&#34;&gt;The first post is here&lt;/a&gt;. And while they aren&amp;rsquo;t indexed, you can still walk through them by clicking on the next posts links. The code formatting didn&amp;rsquo;t survive very well, but if you really want to read 13-year-old technical articles, you gotta expect some rough spots.&lt;/p&gt;&#xA;&lt;p&gt;JavaScript/Canvas remained my weapon of choice for 6 or 7 years. And it&amp;rsquo;s still an occasional second place tool I reach for now and then.&lt;/p&gt;&#xA;&lt;p&gt;What I like about JavaScript&amp;hellip; again, very expressive. It&amp;rsquo;s not fussy and lets you do what you want. In fact, it probably gives you too much leeway. And has more idiosyncracies than ActionScript had. The drawing API for canvas is very capable and very extendable. Zero compile time. It&amp;rsquo;s interpolated. And you just upload and everyone can see and interact with your creation. You hardly need any tooling at all. A simple text editor will suffice. But you can install hard core editors and linters and code completion tools, etc. that will speed up your dev.&lt;/p&gt;&#xA;&lt;p&gt;If you want, you can go with TypeScript, which gives you optional typing and a lot of other advanced language features. But this means you need Webpack or some other system for transpiling, which ups the complexity of your projects significantly.&lt;/p&gt;&#xA;&lt;p&gt;Some of what I did not like so much about JavaScript:&lt;/p&gt;&#xA;&lt;p&gt;While I&amp;rsquo;d done a lot of on-line interactive pieces for nearly two years, I found myself being a bit more interested in creating large, high-res images for various projects, and have wanted to do more with creating static images and even movies, combined with generated audio or music. The JS flow for this was not as good. Working through the browser to create an image or animation on a web page and then saving that to a file on the file system is a pretty horrible idea, but that&amp;rsquo;s what I was doing.&lt;/p&gt;&#xA;&lt;p&gt;Also, while JS has made great strides, it is not the fastest platform out there, especially when you start thinking of creating hundreds, or even thousands of frames for a movie.&lt;/p&gt;&#xA;&lt;p&gt;Finally, I&amp;rsquo;ve mentioned some of the idiosyncracies of JS. They really do get pretty crazy at times. Some very serious WTF moments with how JS interprets code. It&amp;rsquo;s all logical if you look deep enough. I guess. I mean, there are reasons why it does what it does. But those reasons don&amp;rsquo;t make much sense to the average human.&lt;/p&gt;&#xA;&lt;p&gt;So in late 2017, I started looking around for my next language and platform.&lt;/p&gt;&#xA;&lt;h3 id=&#34;python&#34;&gt;Python&lt;/h3&gt;&#xA;&lt;img alt=&#34;python logo&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-09/python.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Python was an initial choice. Python has tons of libraries, and more than a few for drawing 2D graphics and doing animations, including pycairo. But while I&amp;rsquo;d done some work in Python and liked it well enough, I kind of wanted to learn a new language. So I skipped over this one at the time.&lt;/p&gt;&#xA;&lt;p&gt;A couple of times later, as recently as late 2023, I thought about coming back to Python using pycairo. I ported some of my existing stuff to it, but found it painfully slow compared to what I&amp;rsquo;ve been used to over the last few years. So I again abandoned Python as a serious language for creative coding. Just my opinion.&lt;/p&gt;&#xA;&lt;h3 id=&#34;rust&#34;&gt;Rust&lt;/h3&gt;&#xA;&lt;img alt=&#34;rust logo&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-09/rust.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;In last 2017, two languages had caught my attention, Rust and Go. I decided to go for Rust first. I found some Rust bindings for &lt;a href=&#34;https://cairographics.org/&#34;&gt;CairoGraphics&lt;/a&gt;, which is a C-based graphics library - the same one pycairo is based on. I built out a capable graphics library and started doing some fun stuff using Rust. It&amp;rsquo;s a great language for creating apps and services, but I really struggled with it for doing any kind of creative coding. It&amp;rsquo;s extremely strict. It aims to prevent you from doing anything that could create a crash or memory issue. It just won&amp;rsquo;t compile until you do things the exact way Rust demands. Going back to &lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-07/my-creative-workflow-templates/&#34;&gt;my last post&lt;/a&gt;, it was extremely hard to stay in creative mode while fighting with the details of how Rust wanted things to be. It just wasn&amp;rsquo;t going to work for me. That said, others have done creative coding and even game programming in Rust. Power to them. It&amp;rsquo;s not for me.&lt;/p&gt;&#xA;&lt;h2 id=&#34;go&#34;&gt;Go&lt;/h2&gt;&#xA;&lt;img alt=&#34;go logo&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-09/go.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;Click!&lt;/p&gt;&#xA;&lt;p&gt;I dipped my toes into Go in early 2018 and haven&amp;rsquo;t really looked back since. Every once in a while I think about trying something else (see above about Python) and always come back to Go. I stuck with CairoGraphics using a library I found called &lt;a href=&#34;https://github.com/ungerik/go-cairo&#34;&gt;go-cairo&lt;/a&gt; and started building my own library on top of that. For whatever reason, the language just clicked with me. Although strictly typed, it often doesn&amp;rsquo;t feel like it is. It gets out of your way and lets you code. The syntax is mostly easy to pick up. It&amp;rsquo;s FAST. It is a compiled language, but you can run it like a scripting language - &lt;code&gt;go run main.go&lt;/code&gt;. This compiles your code to an executable in a temp folder and then runs it. The compilation is generally so fast that it feels instantaneous, like you&amp;rsquo;re just interpreting a file - with a very fast interpreter.&lt;/p&gt;&#xA;&lt;p&gt;Cairo is pretty much a superset of HTML Canvas&amp;rsquo;s drawing API. Very capable and very extendable. The library I was using was not in active development or maintenance, and after submitting a few updates (which were merged in), I finally just forked it and made my own version which I maintain for myself at least - &lt;a href=&#34;https://github.com/bit101/blcairo&#34;&gt;blcairo&lt;/a&gt;. Beyond just cairo bindings, it adds all kinds of custom drawing routines and an animation framework for creating still images, frame sequences, gifs, videos, sprite sheets, or image montages. Beyond that I created another library, &lt;a href=&#34;https://github.com/bit101/bitlib&#34;&gt;bitlib&lt;/a&gt;, which has custom math, geometry, easing, random, color and noise modules. This is in constant development. Almost every project I do winds up with some update to one or both of these libraries. The two of them are my bread and butter and I&amp;rsquo;ll talk more about them in a future post (or two).&lt;/p&gt;&#xA;&lt;p&gt;Beyond the language itself, what I like about my current workflow is that it&amp;rsquo;s code -&amp;gt; executable -&amp;gt; image or animation on disk. Liberating myself from the browser was one of the main things I was after in 2017 when I decided to move away from JS.&lt;/p&gt;&#xA;&lt;p&gt;One of the things I really miss about Flash and JS though is that interactivity. I can generate and upload animated gifs or videos or images and people can see them, and it&amp;rsquo;s great. But sometimes I miss the days of making things people could play with and interact with and explore on their own, sometimes wasting hours on them. Maybe someday I&amp;rsquo;ll get back into that, but I feel like I&amp;rsquo;m on the right path for now.&lt;/p&gt;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;As I said in the beginning of this post, finding the right language and platform is pretty important to your workflow and productivity and even your creative potential. Don&amp;rsquo;t just do what everyone else is doing. Explore and find out what works for you. Hopefully something will click.&lt;/p&gt;&#xA;&lt;p&gt;Next up I&amp;rsquo;ll talk about another piece of my workflow, which I started to touch on here - libraries.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.flickr.com/photos/simonov/476780331&#34;&gt;Header image &amp;#39;Computer books&amp;#39; by simonov is licensed under CC BY-SA 2.0.&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>My Creative Workflow - Templates</title>
      <link>https://bit-101.com/blog/posts/2024-01-07/my-creative-workflow-templates/</link>
      <pubDate>Sun, 07 Jan 2024 12:59:33 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-07/my-creative-workflow-templates/</guid>
      <description>&lt;h3 id=&#34;art-and-code&#34;&gt;Art and Code&lt;/h3&gt;&#xA;&lt;p&gt;Many years ago I did a conference talk - at a few different conferences - entitled &amp;ldquo;Art and Code&amp;rdquo;. A lot of it had to do with fostering a creative technical workflow. I discussed a bit about left/right brain modes. Although I don&amp;rsquo;t buy into that stuff quite as much as a lot of the pop psychology books and articles do, I think you can be in a creative mode or in an analytical mode.&lt;/p&gt;&#xA;&lt;p&gt;When you&amp;rsquo;re forced into decisions about, say, where to create a project, what to name it, how to structure it, etc., when you have to create a bunch of different folders and files and list out dependencies, etc., you move into that analytical mode.&lt;/p&gt;&#xA;&lt;p&gt;You have a spark of a great idea and you want to try it out in code. You&amp;rsquo;re in create mode. If it takes you five minutes to launch your editor and set everything up before you&amp;rsquo;re able to start coding it, you may be losing that spark you started out with. Sometimes just the thought of setting all that up is enough to make you say &amp;ldquo;forget it&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;The best possible situation is: click a button, start coding. This is close to how the original Flash IDE used to be back in the day. Start Flash, open the Actions Panel, write code. Hit control-enter and see your creation. The Processing IDE is much the same. Neither of these made you decide where to put your project or what to name it. None of them required any  serious boilerplate project structures.&lt;/p&gt;&#xA;&lt;p&gt;Since moving away from Flash, I&amp;rsquo;ve tried to replicate that fast-start project workflow in whatever technical platform I&amp;rsquo;m doing creative coding in. It&amp;rsquo;s a very different workflow from traditional app development, where it&amp;rsquo;s fine to spend 30 minutes or an hour setting up a project, because you&amp;rsquo;ll be working on it for weeks or months or maybe years. So most general dev tools do not cater to this kind of setup.&lt;/p&gt;&#xA;&lt;h3 id=&#34;project-maker&#34;&gt;Project Maker&lt;/h3&gt;&#xA;&lt;p&gt;As we moved beyond Flash and Processing, projects have gotten more complex, with all kinds of structure and dependencies and configuration involved. There&amp;rsquo;s rarely just a blank slate that you can start from and generate an image or animation with a couple lines of code.&lt;/p&gt;&#xA;&lt;p&gt;In this new world, I&amp;rsquo;ve found that project templates are a key tool to get coding as quickly as possible.&lt;/p&gt;&#xA;&lt;p&gt;After I moved off of the Flash IDE, I worked with tools like Flash Develop or Flex Builder. As I recall, these both had some support for templates built in, and a way to create your own custom templates. But when I moved over to JavaScript and HTML for creative coding, I found that I was going to have to roll my own solution.&lt;/p&gt;&#xA;&lt;p&gt;One of my first solutions for this was a plugin I made for Sublime Text back in 2012. &lt;a href=&#34;https://github.com/bit101/ProjectMaker&#34;&gt;ProjectMaker&lt;/a&gt;. It was taken over by someone else after I stopped using it, but I don&amp;rsquo;t know if any of the 36 forks are still active.&lt;/p&gt;&#xA;&lt;img alt=&#34;project maker github repo&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-07/projectmaker.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;With the plugin, you just had to start Sublime, and select a project template. You still had to choose a name and location for your project, but after that it would build out the whole project based on whatever template you chose, and drop you into the main file, ready to code.&lt;/p&gt;&#xA;&lt;p&gt;I used this a lot for JavaScript-based creative coding projects. My template had the HTML, JS and CSS files, and pulled in any dependancies I used. The main JS file was all set up for me to start drawing things on an HTML Canvas.&lt;/p&gt;&#xA;&lt;h3 id=&#34;tinpig&#34;&gt;Tinpig&lt;/h3&gt;&#xA;&lt;p&gt;After I stopped using Sublime Text, I needed that functionality again, and didn&amp;rsquo;t want it tied to any particular editor. So I created &lt;a href=&#34;https://github.com/bit101/tinpig&#34;&gt;tinpig&lt;/a&gt;. This has roughly the same functionality as Project Maker, but is a standalone app that can make projects for any platform, language or IDE. It&amp;rsquo;s bundled with a few sample templates, but it&amp;rsquo;s more powerful when you create your own. The templates can have variables, which you will provide values for when you create your project. It&amp;rsquo;s all interactive, so just running the command walks you through everything you need and the result is a ready to run project.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s what it looks like:&lt;/p&gt;&#xA;&lt;img alt=&#34;tin pig first screen&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-07/tinpig01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Most of these are custom projects I&amp;rsquo;ve set up for myself. The top one, &amp;ldquo;BLCairo Project&amp;rdquo; is the one I use most often. When I hit enter on that, I get a few prompts I have to fill in:&lt;/p&gt;&#xA;&lt;img alt=&#34;tin pig prompts and results&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-07/tinpig02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And here&amp;rsquo;s a look at what that created.&lt;/p&gt;&#xA;&lt;img alt=&#34;tin pig prompts and results&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-07/tinpig03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;It&amp;rsquo;s got a sample animation coded up, so I just have to go into the new project directory and type make and it creates an animation and immediately launches and plays it. This just lets me know that the project is set up correctly and that it runs and compiles and produces what it is supposed to.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a real time video of creating a new project from start to showing the rendered test animation.&lt;/p&gt;&#xA;&lt;video controls tabindex=&#34;-1&#34;&gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2024-01-07/workflow2024.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;The template itself I&amp;rsquo;ll discuss in another post. Of course, having &lt;em&gt;good&lt;/em&gt; templates is vital, but first of all you should set up some kind of project templating so you&amp;rsquo;re not thinking about all that when you have an idea and need to get it down in code.&lt;/p&gt;&#xA;&lt;p&gt;Tinpig is created in node.js and is on npmjs as &lt;a href=&#34;https://www.npmjs.com/package/tinpig&#34;&gt;https://www.npmjs.com/package/tinpig&lt;/a&gt;. So you can install it with one line.&lt;/p&gt;&#xA;&lt;h3 id=&#34;can-i-do-better&#34;&gt;Can I do better?&lt;/h3&gt;&#xA;&lt;p&gt;I admit neither of these solutions are &amp;ldquo;hit a key and start typing&amp;rdquo;. Even with tinpig, you still have to choose where to create your project, give it a name and answer some other questions. But it does a LOT of the heavy lifting required on most current workflows, so I&amp;rsquo;m pretty happy with it.&lt;/p&gt;&#xA;&lt;p&gt;I once had a system that would create a project in a predefined location with a timestamped name. This is roughly what Processing does. That was really great for near instantaneous coding ramp up, but I wound up with a bunch of project folders that gave no clue what was in them. It was good for one-off ideation, but usually I find that I WANT to name something if it&amp;rsquo;s going to stick around for more than a few minutes.&lt;/p&gt;&#xA;&lt;p&gt;I also had a tool that I created in Go that I was using for a while. It was pretty similar to tinpig, but specifically tailored for creating Go projects with my libraries. But more recently I returned to tinpig, cleaned it up a bit and really love it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;resources&#34;&gt;Resources&lt;/h3&gt;&#xA;&lt;p&gt;Some previous posts where I talked about these things:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.bit-101.com/2003/?p=3512&#34;&gt;My original post on ST Project Maker from 2012&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.bit-101.com/2017/2018/03/introducing-tinpig/&#34;&gt;My original post on tinpig from 2018&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.bit-101.com/2017/2018/04/tinpig-1-4-0-and-sales-pitch/&#34;&gt;Another post about tinpig from 2018&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.npmjs.com/package/tinpig&#34;&gt;tinpig on npmjs.com&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/bit101/tinpig&#34;&gt;tinpig on github&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I remember when I created Project Maker, there was discussion around a couple other similar tools that were popular. I don&amp;rsquo;t remember what they were and can&amp;rsquo;t currently find anything else that does something like this. If you know of something similar, let me know (see Mastodon links below) and I&amp;rsquo;ll be happy to add it here.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m also just curious what other people do for this. I&amp;rsquo;m hoping people are not manually creating complex projects from scratch every time. Do you have a templating system and custom project templates? Does the IDE or editor you use have templates built in? Do you just copy an old project and delete stuff in it and start again? Do you have a bare bones project that you copy and paste?&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;I did find a couple of other potentially similar projects. I&amp;rsquo;m not familiar with them, but if you&amp;rsquo;re looking for a solution, these might fit your needs&amp;hellip;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.yellicode.com/&#34;&gt;Yellicode&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;http://www.telosys.org/&#34;&gt;Telosys&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Offhand, I&amp;rsquo;d say these are both a lot heavier than tinpig and use the term &amp;ldquo;code generation&amp;rdquo; a lot. So I think these go beyond the goals of tinpig.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.pexels.com/photo/brown-cookie-cutters-on-the-table-10489043/&#34;&gt;Header image by Nati&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Genuary 2024, Days 1-6</title>
      <link>https://bit-101.com/blog/posts/2024-01-06/genuary-1-6/</link>
      <pubDate>Sat, 06 Jan 2024 05:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-06/genuary-1-6/</guid>
      <description>&lt;p&gt;Well, &lt;a href=&#34;https://genuary.art&#34;&gt;genuary 2024&lt;/a&gt; is underway and I&amp;rsquo;ve made it to day 6! Here&amp;rsquo;s what I&amp;rsquo;ve done so far:&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-1---particles-lots-of-them&#34;&gt;Day 1 - Particles, lots of them&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day01.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Particles are old friends of mine. But I tried to do something a bit different here. I&amp;rsquo;d added some good old Robert Penner style tweening functions to my graphics library some time last year. But I never got a chance to use them. This was an opportunity. Thousands of particles tween between four different arrangements, with some jittering going on as well. The square and circle are just distributions within those shapes. For the words, I created another surface and drew the text onto that. Then randomly generated points, keeping them only if they hit the pixels of the text. Now I had four sets of points, just had to tween from one to the next, and so on.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-2---no-palettes&#34;&gt;Day 2 - No Palettes&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day02.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;I was trying to think of a way to get some strange color combinations by morphing between multiple points of defined colored values with other formulas going on. didn&amp;rsquo;t really get to where I wanted it, but it&amp;rsquo;s fine.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-3---droste-effect&#34;&gt;Day 3 - Droste effect&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day03.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;A silly one. A rare case of me actually using photographs in my work - a selfie even! OMG. A couple of things I could have done better on this. One is that I should have started with a higher res source image so I could start the first sub-image smaller and have it scale up to full size, rather than having it overscale and then snap into focus when it loops. The other one is to have smoother scaling. It&amp;rsquo;s smooth mathematically, but not perceptually. It&amp;rsquo;s a known formula when zooming in on, say, fractals. I&amp;rsquo;ve done it before but got lazy here.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-4---pixels&#34;&gt;Day 4 - Pixels&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day04.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;An old technique that I love. Sample each pixel of a source image and draw it to a destination image which gets displayed. Before you sample, you apply some kind of distortion to the coordinate you&amp;rsquo;re about to sample, but you draw it to the undistorted position in the destination. In this case, the distortion is some Simplex noise with varying parameters.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-5---in-the-style-of-vera-molnar&#34;&gt;Day 5 - In the style of Vera Molnar&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day05.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;At some point in the last 5-10 years it became very trendy for generative artists to name Vera Molnar as an influence. OK. Nothing against her. Maybe something against trends. I liked &lt;a href=&#34;https://digitalartarchive.siggraph.org/artwork/vera-molnar-variations-sainte-victoire/&#34;&gt;this piece&lt;/a&gt; though, so I played around with some ideas and it turned into what I created here. A line of points is drawn at the top of the screen. The points are moved down a bit and randomized a bit. Repeat to the bottom of the screen. Intensify the randomization over time.&lt;/p&gt;&#xA;&lt;h3 id=&#34;day-6---screensaver&#34;&gt;Day 6 - Screensaver&lt;/h3&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/genuary2024/day06.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;p&gt;Flying toasters was too obvious. I made a screen. And tried to save it. Perlin noise on top of Simplex noise. (Or vice versa? I forget.) And some cheesy skeumorphism. I do kind of like the screen reflection. :)&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Creating a Hugo Theme Part 4</title>
      <link>https://bit-101.com/blog/posts/2024-01-02/hugo-themes-part-4/</link>
      <pubDate>Tue, 02 Jan 2024 06:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-02/hugo-themes-part-4/</guid>
      <description>&lt;p&gt;Posts in this series:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2023-12-30/hugo-themes-part-1&#34;&gt;Part 1&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2023-12-31/hugo-themes-part-2&#34;&gt;Part 2&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-01/hugo-themes-part-3&#34;&gt;Part 3&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Part 4&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;We&amp;rsquo;ve got a functional site now and we can add all the content we want to it and it should pretty much show up when and where it&amp;rsquo;s supposed to. Now we just need to make it look good.&lt;/p&gt;&#xA;&lt;p&gt;Now styling a web site can be a very personal thing. And let&amp;rsquo;s face it, that&amp;rsquo;s where all the fun is. So I&amp;rsquo;m not going to build out some in depth style system for you to copy over. I&amp;rsquo;m just going to go through the mechanics and give a few examples and turn it over to you to build out.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m also going to use plain old vanilla CSS. It&amp;rsquo;s not that I&amp;rsquo;m against frameworks or anything. I&amp;rsquo;m just going to leave that choice up to you.&lt;/p&gt;&#xA;&lt;p&gt;But before we even touch CSS, there&amp;rsquo;s one thing I want to take care of with code.&lt;/p&gt;&#xA;&lt;h3 id=&#34;date-formatting&#34;&gt;Date formatting&lt;/h3&gt;&#xA;&lt;p&gt;Right now, all of our dates look like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;single post with long date format&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-02/hugo01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Not great. We can do better. Hugo has built in date formatting tools. Let&amp;rsquo;s use them.&lt;/p&gt;&#xA;&lt;p&gt;Starting with &lt;code&gt;single.html&lt;/code&gt;, which looks like this now:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;{{ .Title }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{{ .PublishDate }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ if .Params.Tags }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Tags: {{ partial &amp;#34;tags.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;{{ .Content }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can set a date format right there, like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;{{ .Title }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{{ time.Format &amp;#34;January 2, 2006&amp;#34; .PublishDate }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ if .Params.Tags }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Tags: {{ partial &amp;#34;tags.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;{{ .Content }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, we call the &lt;code&gt;time.Format&lt;/code&gt; function, passing in the string &amp;ldquo;January 2, 2006&amp;rdquo;, and then the date we want to format. Why &amp;ldquo;January 2, 2006&amp;rdquo;? The date format library takes specific days and months and years, etc. to serve as an example formula. It&amp;rsquo;s designed to be more intuitive than arcane time formatting strings like &lt;code&gt;&amp;quot;%B %d, %Y&amp;quot;&lt;/code&gt;. Here are the formatting components you can use:&lt;/p&gt;&#xA;&lt;img alt=&#34;documentation chart of date/time formatting elements&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-02/hugo02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You can read more about it in the &lt;a href=&#34;https://gohugo.io/functions/time/format/#layout-string&#34;&gt;Hugo docs&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Now the dates should look like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;single page with sensible date format&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-02/hugo03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Try other date format strings like &amp;ldquo;Jan 2, 2006&amp;rdquo; or &amp;ldquo;1/2/06&amp;rdquo; and choose a format that you like. You might even want to include the exact time a post was published.&lt;/p&gt;&#xA;&lt;p&gt;And don&amp;rsquo;t forget to do the same thing in the &lt;code&gt;listitem.html&lt;/code&gt; partial.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ .RelPermalink }}&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {{ .Title }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ if .PublishDate }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{{ time.Format &amp;#34;January 2, 2006&amp;#34; .PublishDate }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ if .Params.Tags }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Tags: {{ partial &amp;#34;tags.html&amp;#34; . -}}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ if .Summary }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Summary: {{ .Summary }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So your posts list will have the right format too:&lt;/p&gt;&#xA;&lt;img alt=&#34;posts list with sensible date format&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-02/hugo04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;As with many things in programming, there are multiple ways to do this. Here&amp;rsquo;s an alternate, even simpler syntax:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{{ .PublishDate.Format &amp;#34;January 2, 2006&amp;#34; }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You might even want to go all in with the HTML &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; element. You can &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time&#34;&gt;read about the time element here&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;OK, enough about dates, onto styles!&lt;/p&gt;&#xA;&lt;h3 id=&#34;css-styles&#34;&gt;CSS styles&lt;/h3&gt;&#xA;&lt;p&gt;As I said, I&amp;rsquo;m not going to do a deep CSS dive. I&amp;rsquo;ll just point you in the direction and send you off to do your magic.&lt;/p&gt;&#xA;&lt;p&gt;Generally, you want to put your CSS in a folder called &lt;code&gt;static&lt;/code&gt; at the top level of your theme. Anything that&amp;rsquo;s in &lt;code&gt;static&lt;/code&gt; will show up in the root of your site. so if you have something like &lt;code&gt;static/styles/style.css&lt;/code&gt;, you&amp;rsquo;ll find that at &lt;code&gt;localhost:1313/styles/style.css&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Within &lt;code&gt;static&lt;/code&gt; you might also want to put a &lt;code&gt;js&lt;/code&gt; folder if there&amp;rsquo;s going to be JavaScript on your site, and maybe &lt;code&gt;images&lt;/code&gt; for any theme-level images or icons you want to use. Anyway, at this point, create the folders and file for &lt;code&gt;static/styles/style.css&lt;/code&gt;. Your theme should look something like this now (I also added &lt;code&gt;js&lt;/code&gt; and &lt;code&gt;images&lt;/code&gt;):&lt;/p&gt;&#xA;&lt;img alt=&#34;theme folder structure&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-02/hugo05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now we need to make sure our styles load into every page we create. If you guessed we&amp;rsquo;ll be doing something in &lt;code&gt;baseof.html&lt;/code&gt;, you are close. If you guessed the &lt;code&gt;head.html&lt;/code&gt; partial, you are spot on. Just link your stylesheet there:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;link&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;rel&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ absURL `styles/style.css` }}&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;My site&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I used the &lt;code&gt;absURL&lt;/code&gt; function to make sure the link is correctly referenced even if the site is not at the root of the web server. You can &lt;a href=&#34;https://gohugo.io/functions/urls/absurl/&#34;&gt;read more about absURL here&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Now we are ready to make some styles! The one thing that bugs me the most about the site now is the default bulleted lists. Let&amp;rsquo;s get rid of the bullets and just put a border around those elements to set them off.&lt;/p&gt;&#xA;&lt;p&gt;In &lt;code&gt;listitem.html&lt;/code&gt;, let&amp;rsquo;s add a &lt;code&gt;list-item&lt;/code&gt; class to the list item element.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;list-item&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ .RelPermalink }}&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {{ .Title }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ if .PublishDate }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{{ time.Format &amp;#34;January 2, 2006&amp;#34; .PublishDate }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ if .Params.Tags }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Tags: {{ partial &amp;#34;tags.html&amp;#34; . -}}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ if .Summary }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Summary: {{ .Summary }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then in &lt;code&gt;styles.css&lt;/code&gt; create that style.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.&lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;list-item&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;list-style&lt;/span&gt;: &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;none&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;border&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;px&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;solid&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;black&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;border-radius&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;px&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;px&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;5&lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;px&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That gives us:&lt;/p&gt;&#xA;&lt;img alt=&#34;styled list items&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-02/hugo06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Here&amp;rsquo;s some more styles I added:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;font-family&lt;/span&gt;: &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;sans-serif&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;#ccf&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;px&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;font-size&lt;/span&gt;: &lt;span style=&#34;color:#f60&#34;&gt;24&lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;px&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This removes any margin and padding from the page, sets a slightly better default font and styles the nav. Now we look like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;styled nav, better fonts&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-02/hugo07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;It&amp;rsquo;s not amazing, but hopefully you agree it&amp;rsquo;s going in the right direction. Not bad for a few very simple lines of CSS. I know you can do better.&lt;/p&gt;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;Well, that wraps up this series. If you learned half as much reading it as I did writing it, then it&amp;rsquo;s all good. If you&amp;rsquo;re just building a single site, it&amp;rsquo;s probably fine to just leave your theme as part of your whole site. If you wind up making a theme you want to re-use or share, it&amp;rsquo;s worth pulling it out into it&amp;rsquo;s own git repo and then adding it to your site using git submodules.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;d also strongly suggest (now that you know how themes work) that you create your themes using the &lt;code&gt;hugo new theme &amp;lt;theme-name&amp;gt;&lt;/code&gt; command line tool. This will create all the necessary folders and some of the boilerplate files, as well as some theme configuration files that we just kind of ignored here. This will be especially important if you start sharing or re-using your themes.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.flickr.com/photos/christiaancolen/20445410340&#34;&gt;Header Image &amp;#39;Binary code&amp;#39; by Christiaan Colen is licensed under CC BY-SA 2.0&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Creating a Hugo Theme Part 3</title>
      <link>https://bit-101.com/blog/posts/2024-01-01/hugo-themes-part-3/</link>
      <pubDate>Mon, 01 Jan 2024 06:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2024-01-01/hugo-themes-part-3/</guid>
      <description>&lt;p&gt;Posts in this series:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2023-12-30/hugo-themes-part-1&#34;&gt;Part 1&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2023-12-31/hugo-themes-part-2&#34;&gt;Part 2&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Part 3&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-02/hugo-themes-part-4&#34;&gt;Part 4&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;What we&amp;rsquo;ve done so far:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Got a home page working (a bit empty, but it&amp;rsquo;s there)&lt;/li&gt;&#xA;&lt;li&gt;Got list pages and single pages working and showing what they should&lt;/li&gt;&#xA;&lt;li&gt;Got a base template working and pulling in all of the above&lt;/li&gt;&#xA;&lt;li&gt;Enabled navigation between all of the parts of the site, including a persistent navigation at the top of each page.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Today&amp;rsquo;s goal is to flesh each of these things out a bit more, and work in a few more pieces of the theme for better organization.&lt;/p&gt;&#xA;&lt;h3 id=&#34;making-a-custom-home-page&#34;&gt;Making a custom home page&lt;/h3&gt;&#xA;&lt;p&gt;Right now our home page is pretty bland. In your final site, you probably want it to talk about your site or yourself or your specific project. But all of that is &lt;em&gt;content&lt;/em&gt; and shouldn&amp;rsquo;t really be in the &lt;em&gt;theme&lt;/em&gt;. So what do you do? One strategy is to make a new home page &lt;em&gt;outside of the theme&lt;/em&gt; that will override the theme home page.&lt;/p&gt;&#xA;&lt;p&gt;In your project directory, you&amp;rsquo;ll notice that Hugo created a few other folders, including a top level &lt;code&gt;layouts&lt;/code&gt; folder. This folder mirrors the theme&amp;rsquo;s &lt;code&gt;layout&lt;/code&gt; folder. And anything you put in here will override whatever is in the theme.&lt;/p&gt;&#xA;&lt;p&gt;Create a new &lt;code&gt;index.html&lt;/code&gt; file inside the top level &lt;code&gt;layouts&lt;/code&gt; folder. In that, define &amp;ldquo;main&amp;rdquo; like we did in the them home template. Within that, you can create any site-specific content you want. Like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;Hello from Keith&amp;#39;s new site!&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;In here will be all kinds of personal stuff that won&amp;#39;t be in the theme.&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now instead of using the theme&amp;rsquo;s home page template, your site will uses this custom one.&lt;/p&gt;&#xA;&lt;img alt=&#34;custom home page&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-01/hugo01.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;partials&#34;&gt;Partials&lt;/h3&gt;&#xA;&lt;p&gt;Partials are like importable chunks of HTML. You can think of them as components that you build the rest of your site with. They&amp;rsquo;re very useful when you need the same chunk of HTML in multiple places, but it can also just help in site organization to have different partials handle specific sections of HTML.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s look at our current &lt;code&gt;baseof.html&lt;/code&gt; file:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;My site&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&amp;gt;Home&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/posts&amp;#34;&lt;/span&gt;&amp;gt;Posts&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/tags&amp;#34;&lt;/span&gt;&amp;gt;Tags&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ block &amp;#34;main&amp;#34; . }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We have three main things going on there.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The head section&lt;/li&gt;&#xA;&lt;li&gt;The nav&lt;/li&gt;&#xA;&lt;li&gt;The main block&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s add a footer as well, just to make it even busier.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;My site&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&amp;gt;Home&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/posts&amp;#34;&lt;/span&gt;&amp;gt;Posts&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/tags&amp;#34;&lt;/span&gt;&amp;gt;Tags&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ block &amp;#34;main&amp;#34; . }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;This is a footer&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can take these sections and split them into individual partial files. Actually, the main section is already split out, so we&amp;rsquo;ll just do the head, nav and footer.&lt;/p&gt;&#xA;&lt;p&gt;In your theme&amp;rsquo;s &lt;code&gt;layout&lt;/code&gt; folder, create a new &lt;code&gt;partials&lt;/code&gt; folder. In that, create &lt;code&gt;head.html&lt;/code&gt;, &lt;code&gt;header.html&lt;/code&gt;, &lt;code&gt;footer.html&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Then take everything in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section of &lt;code&gt;baseof.html&lt;/code&gt; and move it to &lt;code&gt;head.html&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;My site&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Take the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; section and move that to &lt;code&gt;header.html&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&amp;gt;Home&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/posts&amp;#34;&lt;/span&gt;&amp;gt;Posts&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/tags&amp;#34;&lt;/span&gt;&amp;gt;Tags&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And finally, move the footer &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; to &lt;code&gt;footer.html&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;This is a footer&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This leaves our &lt;code&gt;baseof.html&lt;/code&gt; file nearly empty:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ block &amp;#34;main&amp;#34; . }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But we can now pull in those external sections as partials. It looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{- partial &amp;#34;head.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    {{- partial &amp;#34;header.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    {{- block &amp;#34;main&amp;#34; . }}{{- end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    {{- partial &amp;#34;footer.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You just specify the name of the partial file you want to insert. You don&amp;rsquo;t need the path, Hugo knows to look in the &lt;code&gt;partials&lt;/code&gt; folder. And don&amp;rsquo;t forget the dot (&lt;code&gt;.&lt;/code&gt;) to pass in the site context.&lt;/p&gt;&#xA;&lt;p&gt;You&amp;rsquo;ll also note that I threw some hyphens (&lt;code&gt;-&lt;/code&gt;) in there. These serve to strip out any extra white space as you&amp;rsquo;re importing blocks or partials.&lt;/p&gt;&#xA;&lt;p&gt;This is nice because our &lt;code&gt;baseof.html&lt;/code&gt; file is now almost 100% pure template, pulling its content from other places. It&amp;rsquo;s all about the structure of the site. You can see that it all works fine, and we now have a footer on each page as well.&lt;/p&gt;&#xA;&lt;img alt=&#34;post list page showing footer&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-01/hugo02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;If you were to use Hugo&amp;rsquo;s automation to create a new theme (&lt;code&gt;hugo new theme newtheme&lt;/code&gt;), the &lt;code&gt;baseof.html&lt;/code&gt; file it creates looks very similar:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{- partial &amp;#34;head.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {{- partial &amp;#34;header.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {{- block &amp;#34;main&amp;#34; . }}{{- end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;        &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {{- partial &amp;#34;footer.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;div id=&amp;quot;content&amp;quot;&amp;gt;&lt;/code&gt; surrounding the main block is a nice idea. I&amp;rsquo;ll update my &lt;code&gt;baseof.html&lt;/code&gt; to match that. So we&amp;rsquo;ve come to the same place as the automated tool, but step by step. Hopefully you understand exactly what&amp;rsquo;s going on here more than you would have if you just let the tool just do it for you.&lt;/p&gt;&#xA;&lt;h3 id=&#34;improving-the-list-page&#34;&gt;Improving the list page&lt;/h3&gt;&#xA;&lt;p&gt;As it stands, the list page just shows a simple bulleted list of page titles. We can do better. Here&amp;rsquo;s what it looks like now:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ range .Pages.ByPublishDate.Reverse }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ .RelPermalink }}&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {{ .Title }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ll be adding stuff to the &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; list item section, which is already getting big. Good place for another partial. Move the &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; section into a new file, &lt;code&gt;layouts/partials/listitem.html&lt;/code&gt; and replace it with a call to that partial:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ range .Pages.ByPublishDate.Reverse }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{- partial &amp;#34;listitem.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Make sure everything still works.&lt;/p&gt;&#xA;&lt;p&gt;Now let&amp;rsquo;s flesh out that list item. I&amp;rsquo;m just going to throw a bunch of code at you and then walk through it.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ .RelPermalink }}&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {{ .Title }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Date: {{ .PublishDate }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Tags: {{ partial &amp;#34;tags.html&amp;#34; . -}}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Summary: {{ .Summary }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, we keep the &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; tag and put the title in an &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;&#xA;&lt;p&gt;Then we add the publish date in a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;&#xA;&lt;p&gt;Another &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag holds the list of tags. This one gets more complex, so I put it in yet another partial. Partials within partials. Partialception! We&amp;rsquo;ll look at that in a moment.&lt;/p&gt;&#xA;&lt;p&gt;A final &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag holds the summary, which can be one of three things:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The text up to a &lt;code&gt;&amp;lt;!-- more --&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;&#xA;&lt;li&gt;A &lt;code&gt;summary&lt;/code&gt; item in the front matter of the page itself.&lt;/li&gt;&#xA;&lt;li&gt;The first 70 words of the post itself.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;And I believe Hugo will look for those things in that order. So if you have a more tag, it will ignore the summary front matter.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s look at that &lt;code&gt;tags.html&lt;/code&gt; partial next.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ range .Params.tags }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ add (absURL `tags/`) (.) }}&amp;#34;&lt;/span&gt;&amp;gt;{{ . }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we range over the tags and create an anchor tag for each one. The code for getting the path is a bit involved. We want to get the absolute path of &amp;ldquo;tags/&amp;rdquo; first. And then add the tag itself (represented by &lt;code&gt;.&lt;/code&gt;) to that. As I said at the start of this series, I&amp;rsquo;m not going to dive too deeply into Hugo functions and variables.&lt;/p&gt;&#xA;&lt;p&gt;One thing to note here is that since we have nested quotes here, we need to use a different type of quotes to surround &amp;ldquo;tags/&amp;rdquo;. Normally I&amp;rsquo;d use double quotes around the larger block and single quotes for the inner quoted material.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;#34;foo &amp;#39;bar&amp;#39; baz&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But Hugo doesn&amp;rsquo;t like that, so you have to use backticks in this case:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;#34;foo `bar` baz&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And with all that, here&amp;rsquo;s what our post list looks like (truncated):&lt;/p&gt;&#xA;&lt;img alt=&#34;post list showing details about posts&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-01/hugo03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;The usual caveat that it doesn&amp;rsquo;t look so good yet, but all the pieces are in the right places, which is all we&amp;rsquo;re going for here.&lt;/p&gt;&#xA;&lt;p&gt;But remember, the same list template is used for all lists. Including tags. What happens when we look at the tags list?&lt;/p&gt;&#xA;&lt;img alt=&#34;tag list showing too much detail about tags&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-01/hugo04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Ewwwww&amp;hellip; not good.&lt;/p&gt;&#xA;&lt;p&gt;Since tags don&amp;rsquo;t have publish dates, or other tags, or summaries, it doesn&amp;rsquo;t make sense to try to show them. You run into a similar problem if you have a post without any tags. I removed the tags from post five and this is what the list looks like:&lt;/p&gt;&#xA;&lt;img alt=&#34;post list showing empty tag section&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-01/hugo05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;It doesn&amp;rsquo;t make sense to show &amp;ldquo;Tags:&amp;rdquo; when there are no tags. One way around all of this is just to check if there is any content to display before displaying it. I updated the &lt;code&gt;listitem.html&lt;/code&gt; partial to look like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ .RelPermalink }}&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {{ .Title }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h3&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{ if .PublishDate }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Date: {{ .PublishDate }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{ if .Params.Tags }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Tags: {{ partial &amp;#34;tags.html&amp;#34; . -}}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{ if .Summary }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Summary: {{ .Summary }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This cleans things up nicely.&lt;/p&gt;&#xA;&lt;img alt=&#34;tag list showing just the tags&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-01/hugo06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Looking back at the &lt;code&gt;single.html&lt;/code&gt; file, it would be good to show tags here as well. That&amp;rsquo;s pretty easy to add now that we were smart enough to break out tags into a partial.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;{{ .Title }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{{ .PublishDate }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;{{ if .Params.Tags }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  Tags: {{ partial &amp;#34;tags.html&amp;#34; . -}}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;{{ .Content }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now a single page looks like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;single page showing tags&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2024-01-01/hugo07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You might be thinking that both the single and list pages show the date and then the tags. And this might be a good place for another partial, maybe a &amp;ldquo;postmetadata&amp;rdquo; partial that displays date and tags. I&amp;rsquo;ll leave that for you to create if you think it makes sense.&lt;/p&gt;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;We&amp;rsquo;re looking pretty good here now!&lt;/p&gt;&#xA;&lt;p&gt;OK, actually, it looks like crap. But again, everything is where it&amp;rsquo;s supposed to be and working as expected. Structure and functionality are nailed down. In the final post of this series, we&amp;rsquo;ll look at adding some CSS and images to make something worth looking at.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.flickr.com/photos/christiaancolen/20445410340&#34;&gt;Header Image &amp;#39;Binary code&amp;#39; by Christiaan Colen is licensed under CC BY-SA 2.0&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Running Recap</title>
      <link>https://bit-101.com/blog/posts/2023-12-31/running/</link>
      <pubDate>Sun, 31 Dec 2023 13:02:23 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2023-12-31/running/</guid>
      <description>&lt;p&gt;A personal one this time. Back in 2009 I started running pretty seriously. I&amp;rsquo;d run sporadically for some time, but for the previous ten years or so I&amp;rsquo;d been pretty lazy and had gotten pretty heavy. I lost some weight in 2008 and in 2009 started doing the &amp;ldquo;Couch to 5K&amp;rdquo; running program. It stuck and over the next few years I ran a bunch of 5K races, two half marathons, a 20 mile race and three full marathons. I was actually training for an 50K (31 mile) ultramarathon when I just totally burned out on running. I remember the last big training run I did a that time. I ran 3 miles to a park, then 4 miles on the trails there, turned around and ran back home, refilled my bottles and snacks, then headed out in the opposite direction for 5 miles, then home again. Twenty four miles total. I was done for a while.&lt;/p&gt;&#xA;&lt;p&gt;In the years since then, I&amp;rsquo;ve run on and off. I&amp;rsquo;ve gotten some decent streaks going, but never kept at it. In April of this year, I started it up again, and for some reason, it just all fell back into place. I&amp;rsquo;ve run three 5Ks this fall, and I&amp;rsquo;ve been pretty happy with my times. Haven&amp;rsquo;t beat my previous 5K PR, but getting close.&lt;/p&gt;&#xA;&lt;p&gt;I rounded out the year with 530 miles. Not too shabby for starting in April. December was my best month with over 83 miles. My longest runs were 10 miles this year, but I got in four of those.&lt;/p&gt;&#xA;&lt;img alt=&#34;monthly running chart&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/running2023.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Health wise, I think I&amp;rsquo;m doing pretty well. In 2008 I peaked at 235 pounds. During my last bout of serious running I held steady in the low 180s mostly. In the years not running, I was generally around 190-200. These days I&amp;rsquo;ve been holding steady at around 173-174. As a benchmark, I remember being 175 in high school. FWIW, my BMI says I&amp;rsquo;m not overweight, but I&amp;rsquo;m kind of in the upper ranges of healthy weight. I wouldn&amp;rsquo;t mind getting into the 160s.&lt;/p&gt;&#xA;&lt;p&gt;For 2024, mostly more of the same. Keep up steady mileage, see if I can take a few more seconds off my 5K time, and I&amp;rsquo;m considering the idea of a fall half marathon. I&amp;rsquo;d like to do 1000 miles next year, but that means doing 83 miles every single month. I&amp;rsquo;ve done it before, but I&amp;rsquo;ll be happy with 800-900. If I get close near the end of the year, maybe I&amp;rsquo;ll shoot for it.&lt;/p&gt;&#xA;&lt;p&gt;One thing that&amp;rsquo;s been really helpful and supportive is that a bunch of my coworkers run as well. When I was running earlier, I was virtually the only one of everyone I worked with or hung out with that ran. I made a few friends on a site, dailymile.com (sadly now long gone). It was one of the best social networks I have ever been a part of. These days most of my coworkers, as well as other friends are on Strava. It&amp;rsquo;s a similar vibe, though dailymile was a bit more homegrown and cozy where Strava is super slick and overly sponsored. But it&amp;rsquo;s still a great resource. If you&amp;rsquo;re a runner or whatever, &lt;a href=&#34;https://www.strava.com/athletes/990859&#34;&gt;hit me up on Strava&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;gear&#34;&gt;Gear&lt;/h3&gt;&#xA;&lt;p&gt;&lt;strong&gt;Shoes: Brooks Ghost 175&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;img alt=&#34;brooks ghost 15&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/ghost.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;I&amp;rsquo;ve been using Brooks for a long time. Mostly Adrenaline GTXs. Decided to switch it up for something lighter. Only had them for a week or so, but really like them!&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Watch: Garmin Forerunner 255&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;This watch is amazing. I&amp;rsquo;ve had Garmins from 2009 on. Probably five different models. My previous running watch was a Forerunner 235. Also really good, but the 255 has some amazing new features I couldn&amp;rsquo;t pass up. I wound up giving my 235 to a coworker who just ran her first half marathon with it recently. She was running with an Apple watch that would crash every time it rained or got wet.&lt;/p&gt;&#xA;&lt;img alt=&#34;garmin 255&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/garmin.jpg&#34; /&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Creating a Hugo Theme Part 2</title>
      <link>https://bit-101.com/blog/posts/2023-12-31/hugo-themes-part-2/</link>
      <pubDate>Sun, 31 Dec 2023 06:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2023-12-31/hugo-themes-part-2/</guid>
      <description>&lt;p&gt;Posts in this series:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2023-12-30/hugo-themes-part-1&#34;&gt;Part 1&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Part 2&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-01/hugo-themes-part-3&#34;&gt;Part 3&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-02/hugo-themes-part-4&#34;&gt;Part 4&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In Part 1 we got the basic functional parts of a Hugo theme in place. It didn&amp;rsquo;t pull in any actual content or style anything, but it was enough to verify that everything was working like it should. Now let&amp;rsquo;s start pulling in your own content.&lt;/p&gt;&#xA;&lt;p&gt;Throughout this tutorial, remember that anything in your theme folder should ideally be generic. You might want to use the same theme on a different site for example. But&amp;hellip; nothing is stopping you from building a single-use theme that&amp;rsquo;s very customized for just your site. You do you.&lt;/p&gt;&#xA;&lt;h3 id=&#34;fleshing-out-the-single-page-template&#34;&gt;Fleshing out the single page template&lt;/h3&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s start with the single page template in &lt;code&gt;layouts/_default/single.html&lt;/code&gt;. Currently it looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;this is a single page.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rather than displaying static text, we want to pull the content from the page/post/whatever that we&amp;rsquo;re displaying. This is simple. We can just use the built in Hugo page method, &lt;code&gt;.Content&lt;/code&gt;! This returns the content of the current page, converted to HTML and inserts it into the template when it is rendered.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ .Content }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While we&amp;rsquo;re changing up the code, let&amp;rsquo;s go into the &lt;code&gt;baseof.html&lt;/code&gt; file and remove this line:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;this is the base template&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That was just in there to show it was functioning as expected.&lt;/p&gt;&#xA;&lt;p&gt;Now, in your browser go to &lt;code&gt;localhost:1313/posts/first&lt;/code&gt; and you should see your content!&lt;/p&gt;&#xA;&lt;img alt=&#34;page content showing in browser&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;We can add more to this template. We probably want the title and the publish date at least. And we can start to mark up this content with relevant HTML.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;{{ .Title }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;h2&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;{{ .PublishDate }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;{{ .Content }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yeah, now we have something that&amp;rsquo;s starting to look a bit like a blog!&lt;/p&gt;&#xA;&lt;img alt=&#34;post title, date and content showing&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;We could do more here, but let&amp;rsquo;s leave it like that for a while and move onto the list template.&lt;/p&gt;&#xA;&lt;h3 id=&#34;fleshing-out-the-list-template&#34;&gt;Fleshing out the list template&lt;/h3&gt;&#xA;&lt;p&gt;The list template is used any time Hugo needs to display a list of items as the content of the page. As we have seen, that initially includes lists of pages, tags, or categories.&lt;/p&gt;&#xA;&lt;p&gt;To display a list, we need to get the list of things we want to display, then mark up those items with some HTML to display them. As you might expect, this is going to involve looping through some content and doing the same thing to each item it finds.&lt;/p&gt;&#xA;&lt;p&gt;Go ahead and edit the &lt;code&gt;list.html&lt;/code&gt; template file and remove the placeholder text there.&lt;/p&gt;&#xA;&lt;p&gt;This is about the bare minimum of a list template:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;{{ range .Pages }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  {{ .Title }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;.Pages&lt;/code&gt; method returns a list of pages on the site, and the &lt;code&gt;range&lt;/code&gt; function ranges through these. For each one, it just outputs the title of that page with the &lt;code&gt;.Title&lt;/code&gt; method.&lt;/p&gt;&#xA;&lt;p&gt;When that&amp;rsquo;s in place navigate to &lt;code&gt;localhost:1313/posts&lt;/code&gt; to see a list of your posts. For now, we only have one, but it&amp;rsquo;s showing up there!&lt;/p&gt;&#xA;&lt;img alt=&#34;list page showing one post&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo03.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;At this point, you should go in and make a few more posts. Call them whatever you want and put whatever text in them that makes you happy. When you&amp;rsquo;re done, you should see something more like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;list page showing 5 pages horizontally&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Well, they&amp;rsquo;re showing up, but you probably don&amp;rsquo;t want them all on the same line. And while they are showing up in order, we never specified that explicitly. We should do so. In your list template, make an unordered list element, and put each title in a list item.&lt;/p&gt;&#xA;&lt;p&gt;For the ordering, we can use the &lt;code&gt;.ByPublishDate&lt;/code&gt; to sort them. Usually you want to display the most recent post at the top. So you can add on the &lt;code&gt;.Reverse&lt;/code&gt; function at the end. In the end it should look like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ range .Pages.ByPublishDate.Reverse }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;{{ .Title }}&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we have something that looks like an index of posts.&lt;/p&gt;&#xA;&lt;img alt=&#34;list page showing posts in a bullet list&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo05.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Don&amp;rsquo;t worry about the styling yet. Just focus on getting the structure right.&lt;/p&gt;&#xA;&lt;p&gt;Next, it would be lovely if we could click on one of these items and it would navigate right to that post. We can do that with an anchor tag. There are different methods of getting the URL of a page, but the &lt;code&gt;.RelPermalink&lt;/code&gt; method does the job nicely.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ range .Pages.ByPublishDate.Reverse }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ .RelPermalink }}&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;      {{ .Title }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;li&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;ul&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we have links!&lt;/p&gt;&#xA;&lt;img alt=&#34;list page showing posts as links&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo06.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;And each link will take us right to the correct post! Fantastic.&lt;/p&gt;&#xA;&lt;p&gt;Just to show you how powerful this list template is, let&amp;rsquo;s see it working on tags. We don&amp;rsquo;t even have to write any more code to do that. It&amp;rsquo;s ready! We just need to add some tags. I put some random &amp;ldquo;foo&amp;rdquo;, &amp;ldquo;bar&amp;rdquo; and &amp;ldquo;baz&amp;rdquo; tags on my posts, like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;title: &amp;#34;First Post&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date: 2023-12-29T12:01:19-05:00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;draft: true&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;tags: [&amp;#34;foo&amp;#34;, &amp;#34;bar&amp;#34;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;This is my very first post&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you&amp;rsquo;ve done that, go to &lt;code&gt;localhost:1313/tags&lt;/code&gt; and behold your list of tags:&lt;/p&gt;&#xA;&lt;img alt=&#34;list page showing lists of tags&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo07.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Click on any one of them, like &amp;ldquo;baz&amp;rdquo; and it will direct you to &lt;code&gt;localhost:1313/tags/baz&lt;/code&gt; and will then display a list of all the posts with that tag. Fantastic.&lt;/p&gt;&#xA;&lt;img alt=&#34;list page showing a list of posts with a certain tag&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo08.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;making-a-navigation-ui&#34;&gt;Making a navigation UI&lt;/h3&gt;&#xA;&lt;p&gt;It would be nice to have a menu, say across the top of the site that let us get to different parts of the site, like the home page, the list of posts, the list of tags. This is surprisingly easy with basic HTML.&lt;/p&gt;&#xA;&lt;p&gt;We&amp;rsquo;d like this navigation to appear at the top of every page, so let&amp;rsquo;s put it in &lt;code&gt;baseof.html&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;My site&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&amp;gt;Home&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/posts&amp;#34;&lt;/span&gt;&amp;gt;Posts&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;/tags&amp;#34;&lt;/span&gt;&amp;gt;Tags&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;nav&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ block &amp;#34;main&amp;#34; . }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This simply uses the native &lt;code&gt;nav&lt;/code&gt; element with links showing each section and the path to get there. Here&amp;rsquo;s what it looks like on the posts page:&lt;/p&gt;&#xA;&lt;img alt=&#34;nav list across the top&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-31/hugo09.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;I&amp;rsquo;ll also mention that you can create a more powerful and dynamic menu system in your Hugo config. Read the &lt;a href=&#34;https://gohugo.io/content-management/menus/&#34;&gt;documention on menus&lt;/a&gt;. You&amp;rsquo;d still probably wind up putting some code in the same part of your &lt;code&gt;baseof.html&lt;/code&gt; file. So this is still relevant - especially when we get into &amp;ldquo;partials&amp;rdquo; in the next article.&lt;/p&gt;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;At this point, the site is basically functional. We can navigate all around it, go from any page to any other within a couple of clicks. Our home page works, our list page works on different types of lists, and our single pages work on every post.&lt;/p&gt;&#xA;&lt;p&gt;This is probably a pretty good place to take a break. In Part 3 of this series we&amp;rsquo;ll flesh out some of these pages a little bit more, and we&amp;rsquo;ll wrap up in Part 4 with some styling.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.flickr.com/photos/christiaancolen/20445410340&#34;&gt;Header Image &amp;#39;Binary code&amp;#39; by Christiaan Colen is licensed under CC BY-SA 2.0&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Creating a Hugo Theme Part 1</title>
      <link>https://bit-101.com/blog/posts/2023-12-30/hugo-themes-part-1/</link>
      <pubDate>Sat, 30 Dec 2023 06:35:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2023-12-30/hugo-themes-part-1/</guid>
      <description>&lt;p&gt;Posts in this series:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Part 1&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2023-12-31/hugo-themes-part-2&#34;&gt;Part 2&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-01/hugo-themes-part-3&#34;&gt;Part 3&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-02/hugo-themes-part-4&#34;&gt;Part 4&lt;/a&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Obviously I&amp;rsquo;m pretty new to Hugo, so it may seem presumptuous to start writing a tutorial about it. But the best way to learn is to teach, so this is mostly for me. But if someone else benefits from this, all the better!&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m going to assume that you&amp;rsquo;ve got Hugo installed, or can figure out how to get it installed. That might be as far as you got, and you&amp;rsquo;re wondering where to go from there.&lt;/p&gt;&#xA;&lt;p&gt;Or maybe you&amp;rsquo;ve installed a pre-built theme and got that working, but now you want a bit more control.&lt;/p&gt;&#xA;&lt;p&gt;Or maybe you&amp;rsquo;ve tried to create your own theme and got confused, like I did.&lt;/p&gt;&#xA;&lt;p&gt;This post is mainly about making a custom theme, so I&amp;rsquo;m not going to go too deep into Hugo functions and variables and syntax. Of course I&amp;rsquo;ll have to use that stuff at points, but I&amp;rsquo;m going to assume that either you are familiar with it already, or that you&amp;rsquo;ll be able to look it up and figure it out.&lt;/p&gt;&#xA;&lt;p&gt;I also want to mention that there is a command (&lt;code&gt;hugo new theme mytheme&lt;/code&gt;) that will create all the necessary theme folders and some of the files for you. I don&amp;rsquo;t use that here, instead choosing to build things up piece by piece for a fuller understanding of what each piece does.&lt;/p&gt;&#xA;&lt;h3 id=&#34;create-a-site-and-first-page&#34;&gt;Create a site and first page&lt;/h3&gt;&#xA;&lt;p&gt;Go to the directory you want to create your site and type:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hugo new site mysite&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hugo will create a new directory in that folder and within that will create a bunch of other folders and files. &lt;code&gt;cd&lt;/code&gt; into that folder and take a look around. You should see something like this:&lt;/p&gt;&#xA;&lt;img alt=&#34;folder structure of project&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-30/hugo01.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Now make a new post or page:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hugo new posts/first.md&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;posts&lt;/code&gt; part can be anything. If you&amp;rsquo;re building a blog and want to have posts, then &lt;code&gt;posts&lt;/code&gt; makes sense. If you&amp;rsquo;re building another kind of site that has more of a page paradigm, then use &lt;code&gt;pages/first.md&lt;/code&gt;. Again, it can be anything and it has no meaning other than the fact that it&amp;rsquo;s going to define your navigation and URL paths.&lt;/p&gt;&#xA;&lt;p&gt;Now let&amp;rsquo;s edit your post. It should be in &lt;code&gt;content/posts/first.md&lt;/code&gt; and will look like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#555&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;title:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;first&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;2023&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;12&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;29&lt;/span&gt;T11:&lt;span style=&#34;color:#f60&#34;&gt;43&lt;/span&gt;:&lt;span style=&#34;color:#f60&#34;&gt;54&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;05&lt;/span&gt;:&lt;span style=&#34;color:#f60&#34;&gt;00&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;draft:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#555&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All that stuff is called &amp;ldquo;front matter&amp;rdquo;. It&amp;rsquo;s the metadata of the post or page. Feel free to edit the title and even the date. Keep &lt;code&gt;draft&lt;/code&gt; as &lt;code&gt;true&lt;/code&gt; for now. And add some content below the final &lt;code&gt;---&lt;/code&gt;. This is a markdown file, so if you&amp;rsquo;re familiar with markdown, you should be all set. Save it when you&amp;rsquo;re done.&lt;/p&gt;&#xA;&lt;h3 id=&#34;launching-the-server&#34;&gt;Launching the server&lt;/h3&gt;&#xA;&lt;p&gt;Now you probably want to see your awesome site. So back in the command line, in the root of your new project type:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hugo server -DF --noHTTPCache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first part runs the server which will serve up your new site locally in development mode. The &lt;code&gt;-D&lt;/code&gt; will publish posts that are still in draft mode, and the &lt;code&gt;F&lt;/code&gt; will publish posts that have dates in the future. You probably want to use these two options when you&amp;rsquo;re testing your site in dev mode. But when you&amp;rsquo;re deploying your live site, you&amp;rsquo;ll usually omit those.&lt;/p&gt;&#xA;&lt;p&gt;The dev server you are now running is live updating, so your site will refresh whenever you save anything in the project. The &lt;code&gt;--noHTTPCache&lt;/code&gt; parameter just helps to make sure that all changes are picked up and nothing is cached.&lt;/p&gt;&#xA;&lt;p&gt;You probably want to run the server in a separate terminal or tab from where you&amp;rsquo;re doing your development so it stays running, but available so you can stop and restart it easily as needed.&lt;/p&gt;&#xA;&lt;p&gt;In a browser, go to &lt;code&gt;localhost:1313&lt;/code&gt;. Voila! &amp;hellip; um&amp;hellip; &amp;ldquo;Page Not Found&amp;rdquo;. The server is running, but it&amp;rsquo;s not finding the content. If you&amp;rsquo;re familiar with Hugo or web servers in general, you might try going to &lt;code&gt;localhost:1313/posts&lt;/code&gt; or &lt;code&gt;localhost:1313/posts/first&lt;/code&gt;. Same deal.&lt;/p&gt;&#xA;&lt;img alt=&#34;page not found&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images//2023-12-30/hugo02.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;You may also have noticed some warnings when you started the server, saying it couldn&amp;rsquo;t find layout files and telling you you should make some templates. Well, that&amp;rsquo;s what we&amp;rsquo;re here for!&lt;/p&gt;&#xA;&lt;h3 id=&#34;creating-the-home-page&#34;&gt;Creating the home page&lt;/h3&gt;&#xA;&lt;p&gt;There are a few basic templates that Hugo supports out of the box. The home page, a list page, and a single page. Let&amp;rsquo;s start with the home page.&lt;/p&gt;&#xA;&lt;p&gt;In your project, you&amp;rsquo;ll see a &lt;code&gt;themes&lt;/code&gt; folder. In that, create a folder for your theme. I&amp;rsquo;m going to name it &lt;code&gt;mytheme&lt;/code&gt; but feel free to be more creative.&lt;/p&gt;&#xA;&lt;p&gt;Inside that, create a &lt;code&gt;layouts&lt;/code&gt; folder, and within that create an &lt;code&gt;index.html&lt;/code&gt; file. For now just put some text in there, like &lt;code&gt;this is the home page.&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;You might be tempted to fill this page up with some kind of standard HTML markup. Hold that thought. Keep it simple for now.&lt;/p&gt;&#xA;&lt;p&gt;OK. We have the barest, most minimal, bare-minimal theme ever. Let&amp;rsquo;s tell Hugo to use this theme. Edit &lt;code&gt;hugo.toml&lt;/code&gt; in the root of your project. And add a line to point to your theme. It should look like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;baseURL = &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;http://example.org/&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;languageCode = &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;en-us&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;title = &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;My New Hugo Site&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;theme = &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;mytheme&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Make sure everything is saved. Although the server mostly live-reloads, it often doesn&amp;rsquo;t when you make major file changes. So you might need to refresh your browser, or even stop and restart the server. You should wind up with your new home page successfully displaying, &amp;ldquo;this is the home page.&amp;rdquo; (or whatever you filled it with).&lt;/p&gt;&#xA;&lt;p&gt;We&amp;rsquo;ll flesh this out later, but let&amp;rsquo;s build out the files for single pages and lists first.&lt;/p&gt;&#xA;&lt;h3 id=&#34;creating-list-and-single-page-templates&#34;&gt;Creating list and single page templates&lt;/h3&gt;&#xA;&lt;p&gt;While your home page &amp;ldquo;works&amp;rdquo; now, if you go to &lt;code&gt;localhost:1313/posts&lt;/code&gt; or &lt;code&gt;localhost:1313/posts/first&lt;/code&gt;, you&amp;rsquo;re still going to get &amp;ldquo;Page Not Found&amp;rdquo;. That&amp;rsquo;s because those paths are looking for a list template and a single page template. So let&amp;rsquo;s make them.&lt;/p&gt;&#xA;&lt;p&gt;In your template&amp;rsquo;s &lt;code&gt;layout&lt;/code&gt; folder, create a folder called &lt;code&gt;_default&lt;/code&gt;. In that, create &lt;code&gt;list.html&lt;/code&gt; and &lt;code&gt;single.html&lt;/code&gt; files. Again, keep the content simple, something like, &amp;ldquo;this is a list page&amp;rdquo; and &amp;ldquo;this is a single page&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;You might need to restart the server, but now you should be able to go to &lt;code&gt;localhost:1313/posts&lt;/code&gt; and see your list page, and go to &lt;code&gt;localhost:1313/posts/first&lt;/code&gt; to see your single page.&lt;/p&gt;&#xA;&lt;p&gt;We&amp;rsquo;re getting somewhere!&lt;/p&gt;&#xA;&lt;p&gt;A few more things to note here:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;You can also see the list page for &lt;code&gt;localhost:1313/tags&lt;/code&gt; and &lt;code&gt;localhost:1313/categories&lt;/code&gt; even though you didn&amp;rsquo;t define any tags or categories. Those are built in.&lt;/li&gt;&#xA;&lt;li&gt;If you go to a list that doesn&amp;rsquo;t exist yet, such as &lt;code&gt;localhost:1313/pages&lt;/code&gt; or a specific page that doesn&amp;rsquo;t exist like &lt;code&gt;localhost:1313/posts/second&lt;/code&gt;, you&amp;rsquo;ll get &amp;ldquo;Page Not Found&amp;rdquo; as you should expect.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;creating-a-base-template&#34;&gt;Creating a base template&lt;/h3&gt;&#xA;&lt;p&gt;So far, our HTML pages just consist of bits of raw text. You&amp;rsquo;re probably itching to create &lt;code&gt;DOCTYPE&lt;/code&gt;s and &lt;code&gt;meta&lt;/code&gt; tags and &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, and &lt;code&gt;body&lt;/code&gt; tags, etc. And we could do that in each of our three templates if we wanted. But we&amp;rsquo;d probably be duplicating a lot of stuff, and would need to change that in all three places every time we wanted to update it.&lt;/p&gt;&#xA;&lt;p&gt;There are a couple of obvious strategies to handle this.&lt;/p&gt;&#xA;&lt;p&gt;One is to extract the repeating sections and include those pieces in each of the three templates.&lt;/p&gt;&#xA;&lt;p&gt;The other way is to create a &lt;em&gt;base&lt;/em&gt; template and include each of the three &lt;em&gt;page&lt;/em&gt; templates in the base. Hugo does the latter. The base template is called &lt;code&gt;baseof.html&lt;/code&gt; and lives in the &lt;code&gt;layouts/_default&lt;/code&gt; folder of your theme. Create that now.&lt;/p&gt;&#xA;&lt;p&gt;Now we can fill in all the HTML goodness in this file. Something like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#309&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;My site&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;this is the base template&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Feel free to add whatever meta tags, style and script links, etc. here, but try to keep the content generic. Remember, this template will be used as the base of every page in your site. Later we&amp;rsquo;ll put a header and footer in here, which makes sense.&lt;/p&gt;&#xA;&lt;p&gt;And now we need to insert the content of our other three templates into this base template. To do that, we reference a &lt;code&gt;block&lt;/code&gt;. Now we get into our first taste of Hugo syntax. Our block looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#099&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;charset&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#309&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;My site&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;this is the base template&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    {{ block &amp;#34;main&amp;#34; . }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#d8dada&#34;&gt;&lt;span&gt;    {{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We reference a block called &lt;code&gt;main&lt;/code&gt; within double curly brackets, and pass in the dot &lt;code&gt;.&lt;/code&gt; to it, which in Hugo terms is like passing &amp;ldquo;this&amp;rdquo;. Note that we also need to end this block with the &lt;code&gt;end&lt;/code&gt; tag.&lt;/p&gt;&#xA;&lt;p&gt;Now we need to define this &lt;code&gt;main&lt;/code&gt; block in each our our templates. In each of &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;single.html&lt;/code&gt; and &lt;code&gt;list.html&lt;/code&gt;, surround the text with a block definition tag, like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;this is a single page.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, Hugo will always use &lt;code&gt;baseof.html&lt;/code&gt;. When you&amp;rsquo;re on the home page, it will insert the main block from &lt;code&gt;index.html&lt;/code&gt; into the base. When you&amp;rsquo;re on a list or single page, it will include the main block from one of those.&lt;/p&gt;&#xA;&lt;p&gt;Again, with all these major changes, you&amp;rsquo;ll probably have to restart the server and refresh your browser, but now when you navigate to &lt;code&gt;localhost:1313&lt;/code&gt; or the paths &lt;code&gt;/posts&lt;/code&gt; or &lt;code&gt;/posts/first&lt;/code&gt;, you should see the line, &amp;ldquo;this is the base template&amp;rdquo; &lt;em&gt;and&lt;/em&gt; the text from the individual template shown in the browser.&lt;/p&gt;&#xA;&lt;img alt=&#34;showing base template and list page&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images//2023-12-30/hugo04.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;Note that non-existent paths will still correctly serve &amp;ldquo;Page Not Found&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;In case you&amp;rsquo;re having any issues, here&amp;rsquo;s what your project should look like at this point:&lt;/p&gt;&#xA;&lt;img alt=&#34;project structure&#34; class=&#34;shadowed&#34; src=&#34;https://bit-101.com/blog2024_images//2023-12-30/hugo03.png&#34; /&gt;&#xA;&#xA;&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;&#xA;&lt;p&gt;OK, that&amp;rsquo;s enough for one post. It might be a bit unsatisfying since we didn&amp;rsquo;t actually theme anything or even show our single post yet. We&amp;rsquo;ll get there. But perhaps the most important thing about understanding Hugo theming is knowing all of the components of a theme, what each one does, where they live, and how they work together. We&amp;rsquo;ve covered most of that here today. Next time we&amp;rsquo;ll start displaying actual content, and then work on a bit of styling.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.flickr.com/photos/christiaancolen/20445410340&#34;&gt;Header Image &amp;#39;Binary code&amp;#39; by Christiaan Colen is licensed under CC BY-SA 2.0&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>Community Creative Coding</title>
      <link>https://bit-101.com/blog/posts/2023-12-29/community-creative-coding/</link>
      <pubDate>Fri, 29 Dec 2023 06:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2023-12-29/community-creative-coding/</guid>
      <description>&lt;p&gt;I love it when a community comes together to do something positive and fun. An example is when creative people form some kind of hashtag on social media and post images, animations, or other art. Often these are tied to a particular day like &lt;code&gt;#silentsunday&lt;/code&gt;. Other times they extend in time, with a different theme for each day.&lt;/p&gt;&#xA;&lt;h3 id=&#34;genuary&#34;&gt;genuary&lt;/h3&gt;&#xA;&lt;p&gt;The header image on this post refers to &lt;a href=&#34;https://genuary.art&#34;&gt;genuary&lt;/a&gt;, a community generative art project that provides a prompt for each day of January each year. You create something based on the prompt and post it to your social network of choice, with the given hashtag of the day. It&amp;rsquo;s very loose, with no hard and fast rules. Follow the prompts&amp;hellip; or don&amp;rsquo;t. Do some of them, skip others. It&amp;rsquo;s all about creating stuff and sharing.&lt;/p&gt;&#xA;&lt;p&gt;I plan to participate in genuary this year. I&amp;rsquo;m shooting for doing every day, and I&amp;rsquo;ll post here now and then, with a few days worth of creations each time.&lt;/p&gt;&#xA;&lt;p&gt;Last year I did about a week&amp;rsquo;s worth, but got busy with other things, so didn&amp;rsquo;t finish. Here&amp;rsquo;s a couple I did then:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Day 6: Steal like an artist.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;spiky branches rotating in 3d&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2023-12-29/genuary06-2023.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Day 7: Sample a color palette from your favorite movie/album cover.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;video autoplay loop tabindex=&#34;-1&#34; controls title=&#34;&#34; &gt;&#xA;  &lt;source src=&#34;https://bit-101.com/blog2024_images/2023-12-29/genuary07-2023.mp4&#34; type=&#34;video/mp4&#34;&gt;&#xA;  Your browser does not support the video tag.&#xA;&lt;/video&gt; &#xA;&#xA;&lt;h3 id=&#34;7-days-of-code&#34;&gt;7 Days of Code&lt;/h3&gt;&#xA;&lt;img alt=&#34;7 days of code banner&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-29/7days.png&#34; style=&#34;background-color: black;&#34;/&gt;&#xA;&lt;p&gt;Another one is &lt;code&gt;7 Days of Code&lt;/code&gt; (no longer live). This is a monthly coding challenge with prompts given for just the first seven days of each month. I think it&amp;rsquo;s been on hiatus for a while, but it&amp;rsquo;s back for January 2024. Maybe bad timing as I think a lot of the same people who do 7 days will also do genuary. Here&amp;rsquo;s some stuff I did for 7 days in October 2021. I don&amp;rsquo;t recall the prompts.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-29/7days01-2021.gif&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-29/7days02-2021.gif&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;others&#34;&gt;Others&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve actually set up some of these on a very impromptu basis in past years. Search that social media site with the bird icon for hashtags &lt;code&gt;#GulyIsForJifs&lt;/code&gt; and &lt;code&gt;#MayContainGifs&lt;/code&gt;. These were daily challenges to create an animated gif every day of July and May. No particular theme or prompt on those ones. There are still quite a few up on the aforementioned site. I also have a folder full of gifs tagged &lt;code&gt;#Awegif2021&lt;/code&gt; tag for August 2021, but nothing comes up in searches for that. Maybe I&amp;rsquo;ll do something like that later in the year on Mastodon.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s one I did for those &lt;code&gt;#Awegif2021&lt;/code&gt;&lt;/p&gt;&#xA;&lt;img alt=&#34;&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/2023-12-29/awegif24.gif&#34; /&gt;&#xA;&#xA;&lt;p&gt;Anyway, I&amp;rsquo;m looking forward to genuary. Ping me if you&amp;rsquo;re participating!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Hugo</title>
      <link>https://bit-101.com/blog/posts/2023-12-28/hugo/</link>
      <pubDate>Thu, 28 Dec 2023 12:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2023-12-28/hugo/</guid>
      <description>&lt;p&gt;As I mentioned the other day, I decided on &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; to create the new version of this blog. A few reasons for that.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;It&amp;rsquo;s Go and I love Go.&lt;/li&gt;&#xA;&lt;li&gt;I&amp;rsquo;ve used Hugo before and got along well with it.&lt;/li&gt;&#xA;&lt;li&gt;It&amp;rsquo;s pretty easy to work with, compared to some other systems I&amp;rsquo;ve worked with or attempted to.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;theme-woes&#34;&gt;Theme Woes&lt;/h3&gt;&#xA;&lt;p&gt;One thing that&amp;rsquo;s not fantastic about Hugo is the theme system. Or more to the point, the documentation around it. To be fair, there&amp;rsquo;s a fair amount of documenation on the Hugo site, and the initial installation and quickstart tutorials are great, but after that it&amp;rsquo;s just reference material mostly.&lt;/p&gt;&#xA;&lt;p&gt;I installed a few themes and some were fine, but there were little things I wanted to change about them, and it was not clear at all where or how to do that. To figure it out, I had to find some other resources.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://retrolog.io/blog/creating-a-hugo-theme-from-scratch/&#34;&gt;Creating a Hugo Theme From Scratch&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This post really helped a lot. Although it is just a &amp;ldquo;copy this code block into this file, and this one into this other file&amp;rdquo;, it does create a bare minimum setup to work from. From there I could see which bits of code controlled what and was able to piece things together. Starting with that as a base, I ripped out all the external dependencies and little by little replaced most of what was left. And I learned a lot along the way. I can&amp;rsquo;t say I&amp;rsquo;d recommend starting out by creating your own theme from scratch, but it worked for me and now I have complete understanding and control over how the site looks and functions, and will probably keep iterating on it for some time.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/playlist?list=PLLAZ4kZ9dFpOnyRlyS-liKL5ReHDcj4G3&#34;&gt;Hugo video tutorial series&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;iframe width=&#34;100%&#34; style=&#34;--aspect-ratio:560/360&#34; src=&#34;https://www.youtube.com/embed/videoseries?si=-omhfWEDwIr7iYyJ&amp;amp;list=PLLAZ4kZ9dFpOnyRlyS-liKL5ReHDcj4G3&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; allowfullscreen&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;This series was also very helpful. It&amp;rsquo;s dated, but almost all still relevant. Mike tends to over-explain simple points repeatedly, which is fine, but I found myself saying, &amp;ldquo;yeah, yeah, yeah, I got it!&amp;rdquo; multiple times. :) I threw it on 1.5x speed and watched all 23 of them and learned a lot more.&lt;/p&gt;&#xA;&lt;p&gt;After I got some of those basics down, I was much more comfortable going into the official docs and understanding what they were talking about.&lt;/p&gt;&#xA;&lt;p&gt;Some others resources listed on the &lt;a href=&#34;&#34;&gt;External Resources&lt;/a&gt; page of the Hugo docs site. I haven&amp;rsquo;t gotten into any of these yet, but will check them out over time:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://cloudcannon.com/tutorials/hugo-beginner-tutorial/&#34;&gt;Hugo Beginner Tutorial Series&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://pragprog.com/titles/bhhugo/build-websites-with-hugo/&#34;&gt;Build Websites With Hugo Book&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.manning.com/books/hugo-in-action&#34;&gt;Hugo in Action Book&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;big-oops&#34;&gt;Big Oops&lt;/h3&gt;&#xA;&lt;p&gt;Confession time.&lt;/p&gt;&#xA;&lt;p&gt;In Hugo, you write your content in markdown, which is then converted to HTML and merged with various theme templates, then published as a collection of static HTML pages, assets and CSS. There&amp;rsquo;s a development server which is great for testing locally as you&amp;rsquo;re writing or developing your theme. But when you&amp;rsquo;re ready to publish, you just build the site and upload it to any web server. A common way to do this is with &lt;code&gt;rsync&lt;/code&gt;. So I was doing that to make sure it looked ok live on the web.&lt;/p&gt;&#xA;&lt;p&gt;My previous site was at &lt;a href=&#34;https://bit-101.com/blog&#34;&gt;https://bit-101.com/blog&lt;/a&gt; (where this site is now). So I figured it would be safe to rsync the new site to the root of the web folder. And it would have been safe, but I was using the &lt;code&gt;--delete&lt;/code&gt; option, which deletes any existing files in the destination that aren&amp;rsquo;t in the source. And there went my two previous sites, and a whole bunch of other stuff I had up there.&lt;/p&gt;&#xA;&lt;p&gt;Thankfully, my provider had a backup so I was able to get everything restored. And I now have a local backup of all that stuff.&lt;/p&gt;&#xA;&lt;h3 id=&#34;onward&#34;&gt;Onward&lt;/h3&gt;&#xA;&lt;p&gt;I have to say with the theming stuff out of the way, I really, really, REALLY like blogging in markdown in neovim. And I love running my editor on one half of the screen and the browser on the other half, with the dev server running and hot loading my changes whenever I save. I&amp;rsquo;m sure I will continue to iterate on the theming and on the functional aspects of the blog as well, but for now I&amp;rsquo;m pretty happy where it&amp;rsquo;s at.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Hello Again!</title>
      <link>https://bit-101.com/blog/posts/2023-12-28/hello/</link>
      <pubDate>Thu, 28 Dec 2023 08:00:00 -0500</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/posts/2023-12-28/hello/</guid>
      <description>&lt;p&gt;Yes, it was time for a refresh. Honestly, I just got pretty tired of Wordpress.&lt;/p&gt;&#xA;&lt;p&gt;I hate to be that guy on the internet who complains about software, but I&amp;rsquo;ve really been struggling with Wordpress&amp;rsquo;s Gutenberg engine for a long time. Maybe it&amp;rsquo;s just me. I use vim (neovim), which generally means that I just like to edit text when I&amp;rsquo;m editing text. Gutenberg has this whole visual block paradigm with dropdowns and popups and controls and side menus to control what&amp;rsquo;s in each block and what it looks like.&lt;/p&gt;&#xA;&lt;p&gt;Maybe some people like that paradigm, but it just gets in my way. It feels slow and from what I can see, it is super buggy. Things seem to work one way one time and a different way the next. Generally never how I expect it to work.&lt;/p&gt;&#xA;&lt;p&gt;Well, maybe it&amp;rsquo;s buggy, maybe it&amp;rsquo;s me, but I find myself constantly fighting it and getting frustrated, and I&amp;rsquo;m kind of done with it.&lt;/p&gt;&#xA;&lt;p&gt;Beyond the editing experience, Wordpress is a big, beefy system. You have a database to worry about, hundreds and hundreds of PHP files with functions, and spam filters, and plugins to optimize the whole thing to make up for all the rest of it.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been thinking about this a lot, and a static site just sounds wonderful.&lt;/p&gt;&#xA;&lt;p&gt;So, I&amp;rsquo;m moving over to &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt;, which means I can blog from neovim. I can create posts using markdown and check them into github.&lt;/p&gt;&#xA;&lt;p&gt;The old site will still be available as an archive at &lt;a href=&#34;https://bit-101.com/2017&#34;&gt;https://bit-101.com/2017&lt;/a&gt;. And the earlier site has been revived and lives at &lt;a href=&#34;https://bit-101.com/2003&#34;&gt;https://bit-101.com/2003&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s see how this goes! I&amp;rsquo;m hoping with some reduced friction, I just might post a bit more often.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>About</title>
      <link>https://bit-101.com/blog/pages/about/</link>
      <pubDate>Thu, 28 Dec 2023 00:00:00 +0000</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/pages/about/</guid>
      <description>&lt;p&gt;Welcome to BIT-101. My home on the web since 2001. Initially this started out as a Flash based experimental &amp;ldquo;lab&amp;rdquo;, where I did a Flash experiment almost every day for several years. Usually some kind of interactive animation using math or physics. It was a lot of fun.&lt;/p&gt;&#xA;&lt;p&gt;The blog was started in 2003. &lt;a href=&#34;https://bit-101.com/blog/pages/archive&#34;&gt;Earlier versions&lt;/a&gt; of the blog are still available.&lt;/p&gt;&#xA;&lt;p&gt;Other sites of mine you might want to check out:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://artfromcode.com&#34;&gt;Art from Code&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Not a lot of writing here. Just images and animations. This went through a similar move from WordPress to Hugo in 2024. The earlier site is linked there.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://keithpeters.org&#34;&gt;keithpeters.org&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;This is a personal domain that I grabbed early on. It&amp;rsquo;s gone through a few iterations and spent some time being blank. Currently it&amp;rsquo;s got a Faircamp install with the music I&amp;rsquo;ve been creating.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;books&#34;&gt;Books&lt;/h3&gt;&#xA;&lt;p&gt;I think I&amp;rsquo;ve written or contributed to 16 published books. You can find more about them here: &lt;a href=&#34;https://bit-101.com/blog/posts/2024-01-23/books/&#34;&gt;https://bit-101.com/blog/posts/2024-01-23/books/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;The main one I would suggest now is &lt;a href=&#34;https://bit101.gumroad.com/l/playingwithchaos&#34;&gt;Playing With Chaos&lt;/a&gt;, which I still crack open personally to remember how certain techniques are coded. It&amp;rsquo;s all about fractals, strange attractors, chaos theory, cellular automata. Chock full of code to copy and run and alter.&lt;/p&gt;&#xA;&lt;h3 id=&#34;flash&#34;&gt;Flash&lt;/h3&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s a screenshot of the original Flash site in 2001! When I finally stopped maintaining it in 2005, it had around 800 open source experiments.&lt;/p&gt;&#xA;&lt;img alt=&#34;bit-101 site in 2001&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/about/bit101-2001.png&#34; /&gt;&#xA;&#xA;&lt;p&gt;In 2003, that site won the Experimental category at the Flash Film Festival at the FlashForward Conference in NYC. My award was a rubber arrow (the conference logo), which I still have today!&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;flash forward trophy rubber arrow&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/about/trophy1.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;plaque on trophy: flash film festival. new york city FlashForward 2003. winner &amp;gt; experimental. keith peters. www.bit-101.com&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/about/trophy2.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;programming-professionally&#34;&gt;Programming Professionally&lt;/h3&gt;&#xA;&lt;p&gt;That was my big moment. I got home from New York, and the next day I got fired from my crappy job in the estimating department of a mechanical contracting company. Best thing that ever happened to me. Got a new job as a programmer within a week. Twenty years later, I&amp;rsquo;m still working in tech. These days I manage two engineering teams and oversee another manager on two other teams. I don&amp;rsquo;t write much code at work anymore, but that&amp;rsquo;s OK. I let the kids do all that stuff.&lt;/p&gt;&#xA;&lt;h3 id=&#34;programming-recreationally&#34;&gt;Programming Recreationally&lt;/h3&gt;&#xA;&lt;p&gt;I still write code all the time, mostly for fun or artistic purposes, or the occasional side project. I love learning new languages, techniques, new formulas to make interesting images or animations. That&amp;rsquo;s a lot of what you&amp;rsquo;ll find me writing about here.&lt;/p&gt;&#xA;&lt;h3 id=&#34;writing&#34;&gt;Writing&lt;/h3&gt;&#xA;&lt;p&gt;The original Flash site also lead to me to getting my first authoring opportunity - writing a chapter for Flash Math Creativity. Such an amazing book! Nobody publishes books like this anymore. Someone should.&lt;/p&gt;&#xA;&lt;p&gt;Over the next many years I wrote or contributed to at least sixteen programming books.&lt;/p&gt;&#xA;&lt;img alt=&#34;book: flash math creativity&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/about/flashmathcreativity.jpg&#34; /&gt;&#xA;&#xA;&lt;p&gt;I wrote a bit more about the history of this site and some of my professional experiences in my &lt;a href=&#34;https://www.bit-101.com/2017/2021/09/20-years/&#34;&gt;20 Years&lt;/a&gt; post on the earlier site.&lt;/p&gt;&#xA;&lt;h3 id=&#34;personal&#34;&gt;Personal&lt;/h3&gt;&#xA;&lt;p&gt;I live outside of Boston, Massachusetts in the USA. While I&amp;rsquo;ve spent up to a few months in other locations, New England has always been my home. I grew up south of Boston, lived in Boston and Cambridge for many years, then north of Boston for a while. Now I&amp;rsquo;m out in the western suburbs for the past fifteen years. I&amp;rsquo;ve got four kids from my first marriage, all grown up and scattered across the country, and one daughter still at home, in junior year of college. My oldest daughter just made me a grandfather recently!&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t take many selfies, so this is your chance to get a look at me. From age 21, 42, and 59.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;me at 21 with long hair&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/about/kp_at_21.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;me at 42, overweight&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/about/kp_at_42.jpg&#34; /&gt;&#xA;&#xA;&lt;img alt=&#34;me at 59, old, but in shape&#34; class=&#34;&#34; src=&#34;https://bit-101.com/blog2024_images/about/kp_at_59.jpg&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;I just keep getting more handsome! I&amp;rsquo;m about 60 pounds lighter in the third picture than in the second one. A lot of that is due to running. I ran three marathons in my late 40s. Took some years off after that, but I&amp;rsquo;ve been back into it and logged over 500 miles in 2023. I might shoot for a half marathon in 2024.&lt;/p&gt;&#xA;&lt;p class=&#34;citation&#34;&gt;&lt;a href=&#34;https://www.flickr.com/photos/viriyincy/7524796190&#34;&gt;Header image &amp;#39;To US 101&amp;#39; by Oran Viriyincy is licensed under CC BY-SA 2.0&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title></title>
      <link>https://bit-101.com/blog/search/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>kp@bit-101.com (Keith Peters)</author>
      <guid>https://bit-101.com/blog/search/</guid>
      <description>&lt;link href=&#34;https://bit-101.com/blog/pagefind/pagefind-ui.css&#34; type=&#34;text/css&#34; rel=&#34;stylesheet&#34;&gt;&#xA;&lt;script type=&#34;text/javascript&#34;  src=&#34;https://bit-101.com/blog/pagefind/pagefind-ui.js&#34;&gt;&lt;/script&gt;&#xA;&lt;div id=&#34;search&#34;&gt;&lt;/div&gt;&#xA;&lt;script&gt;&#xA;    window.addEventListener(&#39;DOMContentLoaded&#39;, (event) =&gt; {&#xA;        new PagefindUI({&#xA;          element: &#34;#search&#34;,&#xA;          showSubResults: false,&#xA;          showImages: false,&#xA;        });&#xA;    });&#xA;&lt;/script&gt;&#xA;</description>
    </item>
  </channel>
</rss>

