<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Colby's Blog</title>
        <link>https://colbyr.com/blog</link>
        <description>undefined</description>
        <lastBuildDate>Sat, 31 Oct 2026 04:42:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <ttl>60</ttl>
        <image>
            <title>Colby's Blog</title>
            <url>https://colbyr.com/colbyr.svg</url>
            <link>https://colbyr.com/blog</link>
        </image>
        <copyright>All rights reserved 2026, Colby Rabideau</copyright>
        <atom:link href="https://colbyr.com/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Whistle Switch]]></title>
            <link>https://colbyr.com/blog/whistle-switch</link>
            <guid>https://colbyr.com/blog/whistle-switch</guid>
            <pubDate>Sun, 16 Jan 2022 21:00:00 GMT</pubDate>
            <description><![CDATA[Control Homekit by whistling 🎶]]></description>
            <content:encoded><![CDATA[<p>My last day at RC came and went a while back,
but I finally found a few minutes to tidy up <a href="https://github.com/colbyr/whistle-switch">my whistle project</a>.
Its got some rough edges, but it works a lot better than I expected it to when I started 😅</p>
<p>You can install it from <a href="https://www.npmjs.com/package/whistle-switch">npm</a>.</p>
<pre><code class="language-sh">$ yarn global add whistle-switch
</code></pre>
<p>Fire it up on the same network as you homekit setup, and it should work right out of the box.</p>
<pre><code class="language-sh">$ whistle-switch
🚧 starting listener...
🦾 starting controller!
Sun Jan 16 2022 15:56:42 GMT-0500 (Eastern Standard Time) | [vite] connecting...
Sun Jan 16 2022 15:56:42 GMT-0500 (Eastern Standard Time) | [vite] connected.
Sun Jan 16 2022 15:56:42 GMT-0500 (Eastern Standard Time) | %c * Tone.js v14.7.77 *  background: #000; color: #fff
Sun Jan 16 2022 15:56:42 GMT-0500 (Eastern Standard Time) | The ScriptProcessorNode is deprecated. Use AudioWorkletNode instead. (https://bit.ly/audio-worklet)
</code></pre>
<p>If you're trying to run <code>whistle-switch</code> on a Raspberry Pi, you need to <a href="/blog/chrome-ears-only">tell it where to find chromium</a>.</p>
<pre><code class="language-sh">$ WHISTLE_SWITCH_CHROME_EXECUTABLE_PATH=$(which chromium-browser) whistle-switch
</code></pre>
<p>Then you should be able to <a href="https://github.com/colbyr/whistle-switch#connect-with-homekit">connect the switch to your Homekit setup</a>!</p>
<p></p>
<p>Check it out <a href="https://github.com/colbyr/whistle-switch">on Github</a> for <a href="https://github.com/colbyr/whistle-switch#advanced-configuration">more interesting configuration options</a> and <a href="https://github.com/colbyr/whistle-switch#resetting-the-switch">trouble shooting</a>.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Twitter over RSS]]></title>
            <link>https://colbyr.com/blog/twitter-via-tss</link>
            <guid>https://colbyr.com/blog/twitter-via-tss</guid>
            <pubDate>Mon, 04 Oct 2021 17:47:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I don't go on Twitter much any more.
Somewhere along the line it just lost its daily appeal for me.
Lately, I've had a bit of an RSS renaissance.
RSS reading these days with <a href="https://reederapp.com/">Reeder 5</a> on Mac and iOS is a great experience.
I've discovered that much of the content I want to keep up with is available on RSS already.</p>
<p>But alas, there's still some tweeters I wish I could keep up with.
Talking <a href="https://dontpanic.io">with a couple friends</a> whose tweets I regularly miss,
I found myself wondering if I could get their posts into Reeder.
There are some services that do it already, like <a href="https://feeder.co/">feeder</a> and <a href="https://rss.app/blog/how-to-create-rss-feeds-from-twitter-PMsbbO">rss.app</a>, but they cost $$$.</p>
<p>Those services seem to work just fine, but since I'm at RC, I figured I'd take a crack a building something like that myself.
I might as well spend a few dollars on a Digital Ocean VM and and learn something along the way.
So here it is...</p>
<h2>Introducing <a href="https://tweed.systems/">tweed.systems</a></h2>
<p><a href="https://tweed.systems"><img src="/images/tweed-systems.jpg" alt="Screenshot of tweed.systems"></a></p>
<p>It'll give you an RSS feed for a public twitter account to use with your feed reader of choice!</p>
<p>For example, you can find @recursecenter at https://tweed.systems/recursecenter/tweets.rss.</p>
<p><img src="/images/rc-twitter-in-reeder.jpg" alt="@recursecenter in Reeder"></p>
<p>I'm planning to keep this running in a low-key fashion unless I exhaust my Twitter API rate limit.
You can use it at <a href="https://tweed.systems/">tweed.systems</a> or <a href="https://github.com/colbyr/rssfollow">host your own instance</a> if you're feeling DIY.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Chrome, Ears Only]]></title>
            <link>https://colbyr.com/blog/chrome-ears-only</link>
            <guid>https://colbyr.com/blog/chrome-ears-only</guid>
            <pubDate>Thu, 23 Sep 2021 19:03:00 GMT</pubDate>
            <description><![CDATA[Recording audio with a headless browser and a Raspberry PI]]></description>
            <content:encoded><![CDATA[<p>I've been working on a program to turn on my lights when I whistle a tune.
Now that I have a <a href="https://rc.colbyr.com/whistlee2/">working prototype</a>, I wanted to get it running on an old Raspberry Pi and start testing it around the apartment.</p>
<p>So far I've done most of my work with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio</a>.
It was really helpful to work in a familiar language like JS so I could focus on learning about audio.
Alas, Web Audio is really only available in browser.
There's an incomplete <a href="https://github.com/audiojs/web-audio-api">node implementation</a> and plenty of other audio libraries in JS and languages,
but I didn't really want to rebuild what I have. At least not right now. So I got to thinking, maybe I could run the prototype in a "headless" browser environment.</p>
<h2>Headless Chromium and Puppeteer</h2>
<p>Nowadays <a href="https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md">Chromium</a> (and <a href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chrome</a> and <a href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>) can run in "headless" mode.
The browser runs like normal, it just doesn't show any UI on the screen.
Instead, you can interact with the browser programatically through the <a href="https://chromedevtools.github.io/devtools-protocol/">DevTools protocol</a>.</p>
<p>I decided to try using <a href="https://github.com/puppeteer/puppeteer">puppeteer</a> to automate a headless instance of Chromium with JS.
It's pretty fun.</p>
<pre><code class="language-js">// puppeteer-test.js
const puppeteer = require("puppeteer")

;(async () => {
  const browser = await puppeteer.launch({
    // If you disable the `headless` option, you can watch your script control the browser.
    headless: false,
  })
  const [page] = await browser.pages()
  await page.goto("https://pptr.dev/")
})()
</code></pre>
<p>👀 Check it out.</p>
<p><img src="/images/puppeteer-test.gif" alt="A Puppeteer script controlling a browser window"></p>
<p>I disabled <code>headless</code> to create that GIF.
It makes for easier debugging, but beware!
Some APIs behave differently in headless mode.</p>
<h2>Headless recording</h2>
<p>My whistle detector uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia">getUserMedia</a> to access the computer's microphone and listen for whistles.</p>
<pre><code class="language-js">const micStream = await navigator.mediaDevices.getUserMedia({ audio: true })
</code></pre>
<p>With the UI on, you see a permissions dialog when a new site requests access to your mic.</p>
<p><img src="/images/mic-permission.png" alt="Microphone permission dialog"></p>
<p>Unforunately you can't interact with those dialogs in headless mode, so <code>getUserMedia</code> fails with an error like this.</p>
<pre><code class="language-sh">Uncaught (in promise) NotSupportedError: Not supported
</code></pre>
<p>Luckily, we can override those permission dialogs by passing the <code>--use-fake-ui-for-media-stream</code> flag to puppeteer's instance of Chromium
(<a href="https://peter.sh/experiments/chromium-command-line-switches/">this list</a> of Chromium flags came in <em>real</em> handy).</p>
<pre><code class="language-js">const browser = await puppeteer.launch({
  args: ["--use-fake-ui-for-media-stream"],
  headless: true,
})
</code></pre>
<p>With that flag enabled, microphone access works without user interaction. Probably don't pass that flag to your everyday browser. 🤪</p>
<h2>Running on the Raspberry PI</h2>
<p>At that point, everything seemed to be working on my laptop, so it was time to try it out on the PI!
Alas, it failed immediately.</p>
<pre><code class="language-sh">(node:906) UnhandledPromiseRejectionWarning: Error: Failed to launch the browser process!
/home/pi/rc-playground/whistlee-headless/node_modules/puppeteer/.local-chromium/linux-901912/chrome-linux/chrome: 1: /home/pi/rc-playground/whistlee-headless/node_modules/puppeteer/.local-chromium/linux-901912/chrome-linux/chrome: Syntax error: ")" unexpected
</code></pre>
<p>It turns out puppeteer comes with a version of Chromium built for x86 architectures, but the Pi has an ARM processor.
To run puppeteer on the Pi, you need to tell it where to find an ARM compatible version of Chromium.</p>
<p>Nowadays, the <a href="https://www.raspberrypi.org/software/">Raspberry Pi OS</a> comes bundled with already Chromium installed.
You can find it's path using the <code>which</code> command in the Pi's terminal.</p>
<pre><code class="language-sh">~ ➜ which chromium-browser
/usr/bin/chromium-browser
</code></pre>
<p>Then pass that path into puppeteer's <code>executablePath</code> option.</p>
<pre><code class="language-js">const browser = await puppeteer.launch({
  args: ["--use-fake-ui-for-media-stream"],
  executablePath: "/usr/bin/chromium-browser",
  headless: true,
})
</code></pre>
<h2>It's alive!</h2>
<p>That's it! With those options, you should be able to run a webpage headlessly on a Raspberry Pi with access to the mic.</p>
<p>If you want to try the whistle detector:</p>
<ol>
<li>clone my <a href="https://github.com/colbyr/rc-playground">rc-playground</a> project</li>
<li>run <code>yarn</code> at the root</li>
<li>run <code>yarn</code> in <code>whistlee-headless</code>
run <code>./bin/start-headless.sh</code> from the root</li>
</ol>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Animate Canvas with Generators]]></title>
            <link>https://colbyr.com/blog/animate-canvas-with-generators</link>
            <guid>https://colbyr.com/blog/animate-canvas-with-generators</guid>
            <pubDate>Sat, 18 Sep 2021 17:47:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I've enjoyed working with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">canvas</a> recently.
A couple weeks ago I was making a demo to create the <a href="https://www.recurse.com/">RC</a> logo by drawing squares.</p>
<p>The first version of the code was a bunch of canvas instructions one after the other.</p>
<pre><code class="language-js">const ctx = canvasElement.getContext("2d")
ctx.fillStyle = "white"
ctx.fillRect(0, 0, canvasElement.width, canvasElement.height)
ctx.fillStyle = "black"
ctx.fillRect(10, 290, 280, 80)
ctx.fillRect(30, 270, 240, 20)
// and so on and so forth...
</code></pre>
<p>I wanted a simple animation to illustrate the steps of the program.
I split the logic up into an array of functions and called each one in sequence using <a href="https://developer.mozilla.org/en-US/docs/Web/API/setInterval">setInterval</a>.</p>
<pre><code class="language-js">const instructions = [
  () => {
    ctx.fillStyle = "white"
    ctx.fillRect(0, 0, canvasElement.width, canvasElement.height)
  },
  () => {
    ctx.fillStyle = "black"
    ctx.fillRect(10, 290, 280, 80)
  },
  () => {
    ctx.fillRect(30, 270, 240, 20)
  },
  // ...
]

let step = 0
let intervalId = setInterval(() => {
  if (step &#x3C; instructions.length) {
    instructions[step]()
    step++
  } else {
    clearInterval(intervalId)
  }
}, 500)
</code></pre>
<p>That worked just fine. I guess I had to type "() => {}," a lot but it did the job.
Then it occured to me: maybe instead of a list of functions I could use a generator.</p>
<h2>Generators</h2>
<p>A <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator">generator function</a>
is a special syntax for creating objects that implement the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol">iterable protocol</a>.</p>
<pre><code class="language-js">function* makeGenerator() {
  yield 1
  yield 2
  yield 3
}
const generator = makeGenerator()
</code></pre>
<p>The <code>yield</code> keyword works like return, but each time we call <code>generator.next()</code>
the program starts after the previous <code>yield</code>.</p>
<pre><code class="language-js">generator.next() // {value: 1, done: false}
generator.next() // {value: 2, done: false}
generator.next() // {value: 3, done: false}
</code></pre>
<p>Onces you've made it through all the <code>yields</code>, you can keep calling <code>generate.next()</code> but <code>done</code> will always <code>true</code> and nothing else will happen.</p>
<pre><code class="language-js">generator.next() // {value: undefined, done: true}
generator.next() // {value: undefined, done: true}
generator.next() // {value: undefined, done: true}
</code></pre>
<h2>Putting it together</h2>
<p>Getting back to the logo animation,
I put my canvas instructions into a generator with a <code>yield</code> between each step.</p>
<pre><code class="language-ts">function* makeLogoGenerator() {
  ctx.fillStyle = "white"
  ctx.fillRect(0, 0, canvasElement.width, canvasElement.height)
  yield
  ctx.fillStyle = "black"
  ctx.fillRect(10, 290, 280, 80)
  yield
  ctx.fillRect(30, 270, 240, 20)
  yield
  // ...
}
</code></pre>
<p>Then I call <code>logoGen.next()</code> in <code>setInterval</code>, until the generator is <code>done</code>.</p>
<pre><code class="language-js">const logoGen = makeLogoGenerator()
const intervalId = setInterval(() => {
  if (logoGen.next().done) clearInterval(intervalId)
}, 500)
</code></pre>
<p>Calling <code>generator.next()</code> over time creates a simple animation effect on the canvas.
It works pretty well!</p>
<p>The working demo is <a href="https://developer.mozilla.org/en-US/docs/Web/API/setInterval">here</a> and the source is <a href="https://github.com/colbyr/rc-playground/blob/main/rc-logo/index.html">on GitHub</a>.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA["b" is for "bindings"]]></title>
            <link>https://colbyr.com/blog/b-is-for-bindings</link>
            <guid>https://colbyr.com/blog/b-is-for-bindings</guid>
            <pubDate>Thu, 19 Aug 2021 17:30:00 GMT</pubDate>
            <description><![CDATA[Inspect and remove variable bindings in the Erlang shell]]></description>
            <content:encoded><![CDATA[<p>I'm <a href="https://learnyousomeerlang.com/">learning Erlang</a>. It's been fun! The Erlang shell is a great environment for poking around. You get there by typing <code>erl</code> into the terminal.</p>
<pre><code class="language-erl-repl">Erlang/OTP 24 [erts-12.0.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit] [dtrace]

Eshell V12.0.3  (abort with ^G)
1>
</code></pre>
<p>It might feel familiar if you've used the console in a web browser or a REPL in another language. <a href="https://css-tricks.com/can-copy-console/">Like the browser console</a>, Erlang's shell environment includes handy shortcuts for shell-related activities. These are their stories.</p>
<h2>"b" as in "bindings"</h2>
<p>Variables in Erlang are immutable. You can't change 'em. I knew that going in, but it often trips me up in the shell.</p>
<pre><code class="language-erl-repl">1> One = 1.
1
2> Two = One + One.
2
3> One = 2.
** exception error: no match of right hand side value 2
</code></pre>
<p>Once you define a variable, you can't set it again. Immutable. Right. But sometimes, in a long shell session, I clear my screen or just forgot what variables I set.</p>
<p>Try <code>b/0</code>. It prints out all the variables defined in the current session along with their assigned values.</p>
<pre><code class="language-erl-repl">4> b().
One = 1
Two = 2
ok```

## "f" as in "forget"

Even better, `f/1` will _unbind_ a variable you want to reuse.

```erl-repl
12> b().
One = 1
Two = 2
ok
13> f(One).
ok
14> One = 2.
2
</code></pre>
<p>And if you need a clean slate, <code>f/0</code> removes <em>all</em> variable bindings in the session.</p>
<pre><code class="language-erl-repl">16> b().
One = 2
Two = 2
ok
17> f().
ok
18> b().
ok
</code></pre>
<p>That's all for now. To learn more about the erlang shell, get straight into <a href="https://erlang.org/doc/man/shell.html#shell-commands">the docs</a>.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Starting at RC]]></title>
            <link>https://colbyr.com/blog/starting-at-rc</link>
            <guid>https://colbyr.com/blog/starting-at-rc</guid>
            <pubDate>Fri, 06 Aug 2021 17:30:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I've been brainstorming what I'll work on at <a href="https://www.recurse.com/">The Recurse Center</a> when I start next week. I plan to spend a good amount of time programming but leave some room for serendipity. For now, my idea is to write a program that turns on the lights when I whistle a particular tune.</p>
<p>It's a concrete idea. I <em>think</em> it's achievable with wiggle room for exploration. Apparently it's been done before <a href="https://youtu.be/27HGjE6ucqY?t=67">more</a> <a href="https://www.wired.com/2017/01/classic-mobius-lamp/">than</a> <a href="https://www.tindie.com/products/limpkin/the-whistled-a-whistle-detection-device/">once</a>.</p>
<p>I can imagine the steps to get there: record things, identify a whistle, make a request to the HomeKit API, turn on the lights. The good news is that I have no idea how to do any of that. It's good news from an "I'm trying to learn things" perspective anyways. I can also imagine plenty of ways to take it further if I finish. Like what if the program whistles back?</p>
<p>I want to give Erlang a try for this project. Last week, I read through some of <a href="https://learnyousomeerlang.com/">Learn You Some Erlang for Great Good!</a>. The syntax is funky, but I feel I've already learned a lot from reading through some of the chapters on concurrency and distribution. I'm not sure it's the most practical choice, but I think it'll be fun. I have a couple of Raspberry Pis gathering dust in my closet, so maybe I can set up a lil cluster.</p>
<p>I'll continue writing about what I'm up to at RC, so you can look forward to updates soon. Keep an eye on my projects at <a href="https://rc.colbyr.com/">rc.colbyr.com</a>.</p>
]]></content:encoded>
        </item>
    </channel>
</rss>