<?xml version="1.0" encoding="UTF-8"?><rss 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" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Andrew Chidden]]></title><description><![CDATA[There’s coffee in that VR headset.]]></description><link>http://andrewchidden.com/</link><image><url>http://andrewchidden.com/favicon.png</url><title>Andrew Chidden</title><link>http://andrewchidden.com/</link></image><generator>Ghost 1.19</generator><lastBuildDate>Wed, 08 Apr 2026 10:08:13 GMT</lastBuildDate><atom:link href="http://andrewchidden.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Why can you say abso-frickin-lutely but not ab-frickin-solutely?]]></title><description><![CDATA[<div class="kg-card-markdown"><p>The linguistic process of inserting one word into another is called infixation. Although it’s essential to some languages, infixation in English is largely limited to inserting expletives, which is called expletive infixation.</p>
<img src="https://imgs.xkcd.com/comics/syllable_planning.png" title="You absolute-fucking-... shit." alt="Syllable Planning">
<center style="margin-top:-2em;"><a href="https://xkcd.com/1290/" target="_blank">xkcd #1290</a></center>
<p>Like many grammatical rules, infixation isn’t something that’s explicitly taught (similar to the</p></div>]]></description><link>http://andrewchidden.com/modeling-expletive-infixation/</link><guid isPermaLink="false">5c4e88e9b3511d096dbcaf1a</guid><category><![CDATA[Projects]]></category><category><![CDATA[Linguistics]]></category><dc:creator><![CDATA[Andrew Chidden]]></dc:creator><pubDate>Tue, 29 Jan 2019 08:00:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>The linguistic process of inserting one word into another is called infixation. Although it’s essential to some languages, infixation in English is largely limited to inserting expletives, which is called expletive infixation.</p>
<img src="https://imgs.xkcd.com/comics/syllable_planning.png" title="You absolute-fucking-... shit." alt="Syllable Planning">
<center style="margin-top:-2em;"><a href="https://xkcd.com/1290/" target="_blank">xkcd #1290</a></center>
<p>Like many grammatical rules, infixation isn’t something that’s explicitly taught (similar to the ordering of adjectives). Instead, speakers unconsciously learn the latent rules behind expletive infixation. This remarkably leads to consistency between speakers on…</p>
<ol>
<li><em>Where</em> in a word insertion can occur, if at all.</li>
<li><em>What</em> kind of expletives can be inserted.</li>
</ol>
<p>As for why <em>abso-frickin-lutely</em> works but not *<em>ab-frickin-solutely</em>, the most widely accepted linguistic model proposes that infixation depends on which syllables in a word are stressed, called its prosodic structure.</p>
<p>Using a dictionary of pronunciations and a statistical model of word stress, it’s possible to derive the prosodic structure for almost any word—even made-up ones. So as part of my exploration into computational linguistics, I decided to create a fun micro web app that automates the process of expletive infixation:</p>
<ul>
<li><a href="https://infixer.app"><em>Infixer</em> - https://infixer.app</a></li>
</ul>
<p>Give it a few sentences and some pseudo (or real) expletives and it’ll magically insert them into your words. As a novelty feature, you can even infix emoji, creating monstrosities like <i>abso-</i>😂<i>-lutely</i>.</p>
<p>And if you’re a developer who’s interested in using results from the <em>Infixer</em> for building a something-or-another, I’ve also exposed the API and its documentation.</p>
</div>]]></content:encoded></item><item><title><![CDATA[The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar.]]></title><description><![CDATA[<div class="kg-card-markdown"><p>First released in 2016, the Touch Bar MacBook Pro models received a fair amount of skepticism from the professional community. Two years later and the Touch Bar remains largely unchanged, with third-party adoption of Touch Bar APIs doing little to improve the usefulness for power users. Yet Apple clearly sees</p></div>]]></description><link>http://andrewchidden.com/long-live-the-macbook-pro-with-touch-bar/</link><guid isPermaLink="false">5b73330cb3511d096dbcaeae</guid><category><![CDATA[Projects]]></category><category><![CDATA[Objective-C]]></category><dc:creator><![CDATA[Andrew Chidden]]></dc:creator><pubDate>Tue, 21 Aug 2018 19:01:31 GMT</pubDate><media:content url="http://andrewchidden.com/content/images/2018/08/new-hero-cropped-2-1.jpg" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="http://andrewchidden.com/content/images/2018/08/new-hero-cropped-2-1.jpg" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."><p>First released in 2016, the Touch Bar MacBook Pro models received a fair amount of skepticism from the professional community. Two years later and the Touch Bar remains largely unchanged, with third-party adoption of Touch Bar APIs doing little to improve the usefulness for power users. Yet Apple clearly sees the Touch Bar as the path forward. It’s now up to the end-user to adapt the hardware and software to fit his or her needs.</p>
<p>I submit that the limitations of the Touch Bar come from Apple’s restrictive design guidelines and lack of customization. With the right tools, I believe that it <em>is</em> possible to modify the Touch Bar and make it useful—even for power users.</p>
<h3 id="tableofcontents">Table of Contents</h3>
<ul>
<li><a href="#considerthepoweruser">Consider the Power User</a></li>
<li><a href="#usingthepreset">Using the Preset</a></li>
<li><a href="#optimizingthehigforpowerusers">Optimizing the HIG for Power Users</a></li>
<li><a href="#bettertouchtooldevelopmenttips">BetterTouchTool Development Tips</a></li>
</ul>
<hr>
<h1 id="considerthepoweruser">Consider the Power User</h1>
<p>When read from the perspective of a power user, Apple’s <a href="https://developer.apple.com/design/human-interface-guidelines/macos/touch-bar/touch-bar-overview/#design-considerations">Design Considerations</a> for the Touch Bar make little sense:</p>
<ul>
<li>Make the Touch Bar relevant to the current context on the main screen.</li>
<li>Use the Touch Bar as an extension of the keyboard and trackpad, not as a display.</li>
<li>Don’t expose functionality solely in the Touch Bar.</li>
</ul>
<p>If the Touch Bar constantly changes based on context, then tapping the correct button cannot be done through muscle memory alone. Furthermore, since the Touch Bar cannot provide unique functionality, the benefits of the Touch Bar are very low. At best, the guidelines relegate the Touch Bar to triggering “obscure” keyboard shortcuts and menu items.</p>
<p>But what if the guidelines were read from a casual user’s point of view? Dynamic controls suddenly make sense; casual users might already glance at the keyboard to perform non-standard actions, and such users may not know about advanced features buried behind a menu.</p>
<p>So the problem is clear. Although the design of the Touch Bar may improve the experience for casual users, it does so at the expense of power users.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>The sole prerequisite to improving the Touch Bar for power users is <a href="https://folivora.ai/">BetterTouchTool</a> (BTT), a multi-purpose application that also allows for extensive TouchBar customization. Be warned that BTT is very much designed for power users. Perhaps one day Apple will provide a more streamlined and robust way to manage the Touch Bar, but until then BTT remains the best and only alternative.</p>
<h2 id="buildingonpastwork">Building on Past Work</h2>
<p>Many people have already written about BetterTouchTool and offered unique Touch Bar presets. Most notably:</p>
<ul>
<li><a href="http://vas3k.com/blog/touchbar/">Vasily Zubarev’s post</a> detailing his annoyances with the Touch Bar and a preset containing BTT widgets that he found useful.</li>
<li><a href="https://community.folivora.ai/t/goldenchaos-btt-a-complete-touch-bar-ui-replacement-preset/1281">The GoldenChaos-BTT preset</a> designed to offer “a complete Touch Bar UI replacement.”</li>
</ul>
<p>These presets provide a great starting point for anyone looking to customize the Touch Bar. However, I’d like to present my own heavily-tailored setup which iterates on existing presets as well as Apple’s Human Interface Guidelines (HIG) for the Touch Bar.</p>
<h2 id="goals">Goals</h2>
<p>In designing for my needs as a power user, I kept these goals in mind:</p>
<ol>
<li>Enhance a development-oriented workflow.</li>
<li>Remain un-distracting and aesthetically pleasing.</li>
<li>Keep the power consumption as low as possible.</li>
</ol>
<p>I believe my setup meets all three goals, turning the MacBook Pro with Touch Bar into my preferred development environment.</p>
<h2 id="appregion">App Region</h2>
<p><a href="https://andrewchidden.com/content/images/2018/08/new-appregion-optimized.jpg"><img src="https://andrewchidden.com/content/images/2018/08/new-appregion-optimized.jpg" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<h4 id="gitdiffstatistics">Git Diff Statistics</h4>
<p>When working on a project, I want to know whether the working directory is clean (so I can rebase or check out safely), or the size of my next pull request.</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-screen-appregion-gitdiff-1.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-screen-appregion-gitdiff-1.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<p><strong>Text Label:</strong> Displays the number of files changed and the lines added / removed for both staged and unstaged files.</p>
<p><strong>Single Press:</strong> Opens <a href="http://gitup.co/">GitUp</a>, an open-source Git interface for macOS.</p>
<p><strong>Long Press:</strong> Pushes the current branch and shows the page for creating a new pull request on the branch.</p>
<p><strong>Implementation:</strong> The widget uses caching to reduce locking on the Git index; it first checks <code>git --no-optional-locks status</code> before performing <code>git diff HEAD</code>. See also the <a href="https://git-scm.com/docs/git-status#_background_refresh">Git documentation</a> on best practices for background status refresh.</p>
<h4 id="xcodebuildstatus">Xcode Build Status</h4>
<p>Large Xcode projects can take a long time to build, especially after cleaning intermediaries. (Longer than just grabbing a cup of coffee.) I need a way of checking on the progress of builds and tests, and the status whether it failed or succeeded.</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-screen-appregion-xcode-1.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-screen-appregion-xcode-1.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<p><strong>Text Label:</strong> If building, shows the name of the last intermediate product built, such as a static library. If the build / test failed, it shows the number of errors generated or a readable error message.</p>
<p><strong>Single Press:</strong> Opens or focuses the main project workspace in Xcode.</p>
<p><strong>Long Press:</strong> Reveals the main project folder in Finder.</p>
<p><strong>Implementation:</strong> Xcode doesn’t provide any useful AppleScript hooks for determining current build progress. Rather than using <code>xcodebuild</code> which would break my existing workflow around Xcode, to show progress I instead monitor changes to the intermediary build products in the derived data directory.</p>
<p>Once a build completes or a test suite finishes, the script takes the latest gzipped build log and analyzes it for error messages. I built the system to match my own build pipeline so it is unlikely to work or add value for most developers; I won’t be including this widget in the public preset.</p>
<h4 id="nextcalendarevent">Next Calendar Event</h4>
<p>Seeing the next event and time left allows me to better plan tasks throughout the day. Plus, it gives me reassurance against inadvertently missing a meeting.</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-screen-appregion-calendar-1.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-screen-appregion-calendar-1.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<p><strong>Text Label:</strong> Shows the name of the next calendar event and a readable relative time: the number of hours if more than one hour, the number of minutes if less than an hour. If the event already started, it shows the remaining time left.</p>
<p><strong>Single Press:</strong> Opens the native Calendar application.</p>
<p><strong>Long Press:</strong> Opens the Google Calendar webpage. I find this useful because despite its nice UX, Apple Calendar lacks certain core functionality offered by Google, such as booking meeting rooms.</p>
<p><strong>Implementation:</strong> I wanted the text label to update within seconds whenever the next calendar event changes. Unfortunately, the open-source command line utility <a href="http://hasseg.org/icalBuddy/">icalBuddy</a> which is often used in BTT presets suffers from poor performance, lack of maintenance (last commit was almost five years ago), and must poll the Calendar Agent for changes.</p>
<p>In order to push updates to BTT and reduce overall energy impact, I wrote a custom command line utility that acts as a persistent service between <code>EventKit</code> and BTT. The utility listens for calendar changes and pushes a <code>refresh_widget</code> request to BTT’s local web server where the widget’s controller script handles the appearance update.</p>
<h2 id="controlstrip">Control Strip</h2>
<p><a href="https://andrewchidden.com/content/images/2018/08/controlstrip-optimized.jpg"><img src="https://andrewchidden.com/content/images/2018/08/controlstrip-optimized.jpg" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<h4 id="brightness">Brightness</h4>
<p>Changing the screen brightness is a standard system-level control. To maximize usefulness, the buttons reproduce the native handling of modifier keys and incorporate additional functionality through long press gestures.</p>
<p><strong>Single Press:</strong> Turns screen brightness up / down.</p>
<p><strong>Long Press Brightness Down:</strong> Locks the screen.</p>
<p><strong>Shift Press:</strong> Turns keyboard illumination up / down.</p>
<p><strong>Option Press:</strong> Opens the Displays preference pane in System Preferences.</p>
<p><strong>Option + Shift Press:</strong> Uses small steps to turn screen brightness up / down.</p>
<p><strong>Implementation:</strong> In its default configuration, BTT cannot handle such a complex gesture set. (To be precise, there’s no way to <em>not</em> perform a default button action but also handle modifier keys.) Instead, I wrote a very small (~100 LOC) command line utility that handles modifier key logic and synthesizes the corresponding <code>NSEvent</code> objects.</p>
<p><strong>Here be Dragons:</strong> Events created with type <code>NSEventTypeSystemDefined</code> and posted using <code>CGEventPost(kCGHIDEventTap, ...)</code> must have a magic delay after <em>both</em> the key down and key up events. Not doing so often results in a race condition between the two events.</p>
<h4 id="volume">Volume</h4>
<p>The volume control works similarly to brightness, but also changes appearance based on current volume and mute status. Increasing the volume activates more “sound waves” in the icon:</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-screen-vol-combined-inv-1.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-screen-vol-combined-inv-1.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<p>Unlike the system control, the muted appearance preserves the last icon state; this is particularly useful in indicating if the volume will be loud or soft when unmuting.</p>
<p><strong>Single Press:</strong> Turns volume up / down.</p>
<p><strong>Long Press Volume Down:</strong> Toggles mute / unmute.</p>
<p><strong>Shift Press:</strong> Plays the system feedback sound effect.</p>
<p><strong>Option Press:</strong> Opens the Volume preference pane in System Preferences.</p>
<p><strong>Option + Shift Press:</strong> Uses small steps to turn volume up / down.</p>
<p><strong>Implementation:</strong> The volume control doesn’t just synthesize events, but also updates the icon appearance in real time. It uses the same command line utility as brightness for key presses but also a custom push-based service similar to calendar for volume change events.</p>
<p><strong>Here be Dragons:</strong> To make the control feel responsive, it’s necessary to keep the latency low between a volume change event and the Touch Bar icon updating. I found that sending HTTPS requests to BTT incurred a non-negligible latency overhead compared to HTTP.</p>
<h4 id="clockapp">Clock App</h4>
<p>Hiding the system menu bar frees up valuable vertical screen space, but also hides the time. Moving the clock to the Touch Bar fixes the issue and provides a convenient way to access additional features.</p>
<h4 id="worldclock">World Clock</h4>
<p>I often want to know what time it is in Stockholm or New York City. By tapping the clock, I can immediately see the day of the week, date, and times for points of interest around the world. Tapping the local time again dismisses the modal.</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-screen-worldtimes-2.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-screen-worldtimes-2.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<h4 id="countdowntimer">Countdown Timer</h4>
<p>Timers can provide utility in many contexts, ranging from Pomodoro timers to simple mental reminders. Unfortunately, macOS doesn’t come with a built-in timer application, and certainly not one for the Touch Bar.</p>
<p>Long pressing the clock reveals a minimal set of options to start a timer or cancel the existing one.</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-screen-timerapp.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-screen-timerapp.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<p><strong>Implementation:</strong> I built the timer with simple source-embedded shell scripts to avoid the need to compile yet another custom command line utility.</p>
<h2 id="secondarywidgets">Secondary Widgets</h2>
<p>Workflow macros can offer power users a great deal of utility with very little work or setup. I’ve created a handful of destructive / not undoable macros (in red) as well as a few general-purpose ones (in gray). These macros only show while I hold down the three bottom-row modifier keys (command + option + control).</p>
<p>Each macro simply opens a Terminal window with the pre-defined commands. Macros execute in a Terminal window rather than in the background because visual execution of commands allows error states or those that require user interaction (such as entering an RSA key passphrase) to work seamlessly.</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-screen-secondarylayout.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-screen-secondarylayout.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<hr>
<h1 id="usingthepreset">Using the Preset</h1>
<h2 id="caveats">Caveats</h2>
<p>I designed this preset for my own use-cases and workflow. Power users who have non-development workflows, don’t use trackpad gestures for Exposé, or keep the MacBook Pro in clamshell mode / on a stand may not find much utility in this preset. If you find this to be the case, consider perusing <a href="https://community.folivora.ai/c/setup-preset-sharing">other presets</a> created by the BetterTouchTool community.</p>
<h2 id="installation">Installation</h2>
<ol>
<li>If not already installed, download and install <a href="https://folivora.ai/downloads">BetterTouchTool</a>.
<ul>
<li><strong>UPDATE (Aug 28): The preset was exported and tested using BTT v2.536 and may not work for the recently released BTT v2.6xx</strong>. You can download <code>btt2.536.zip</code> here: <a href="https://bettertouchtool.net/releases/">https://bettertouchtool.net/releases/</a></li>
</ul>
</li>
<li>Download the latest controller scripts and compiled service binaries for the preset: <a href="https://github.com/andrewchidden/btt-controllers">andrewchidden/btt-controllers</a>.
<ul>
<li><code>git clone</code> the repository to have the path <code>~/bettertouchtool</code>.</li>
<li>Alternatively, download the zip archive, decompress, and move + rename as <code>~/bettertouchtool</code>.</li>
</ul>
</li>
<li>Decide on a preset flavor:
<ul>
<li><strong>andrewchidden-with-settings.bttpreset</strong> if new to BetterTouchTool. Contains everything including BetterTouchTool settings to allow the preset to function as intended.</li>
<li><strong>andrewchidden.bttpreset</strong> if already using BetterTouchTool. Only contains controls and triggers. You will need to manually configure parts of the preset and/or BTT after installation.</li>
</ul>
</li>
<li>Download from a mirror:
<ul>
<li>BetterTouchTool (share.folivora.ai)
<ul>
<li><strong>andrewchidden-with-settings.bttpreset</strong> <a href="btt://importviaurl/https://share.folivora.ai/presets/downloadPreset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkb3dubG9hZFVVSUQiOiI2YTcyNjcyNS1lNTlmLTRjMmItYjViMy1hYTYwMzYyYmRlNDEiLCJpYXQiOjE1MzQ4ODUzNDEsImV4cCI6MTUzNDg4ODk0MX0.mYqmAvYPIu26Wjcil9c3lwXlgGdhj0udHah-2B1xzAA">Direct Import</a> or <a href="https://share.folivora.ai/sharedPreset/6a726725-e59f-4c2b-b5b3-aa60362bde41">Source Viewer</a></li>
<li><strong>andrewchidden.bttpreset</strong> <a href="btt://importviaurl/https://share.folivora.ai/presets/downloadPreset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkb3dubG9hZFVVSUQiOiJhZmM3MTEyMS1lZDY2LTQ2NjEtYWJlZS0wYzY2N2U3MjRmMGQiLCJpYXQiOjE1MzQ4ODUzOTcsImV4cCI6MTUzNDg4ODk5N30.mLN7lXpj-3OSpBJZsLDr5X8Sbd8AaxnsZ5VB_Wei3E8">Direct Import</a> or <a href="https://share.folivora.ai/sharedPreset/afc71121-ed66-4661-abee-0c667e724f0d">Source Viewer</a></li>
</ul>
</li>
<li><a href="https://github.com/andrewchidden/btt-presets">andrewchidden/btt-presets</a> (github.com)</li>
</ul>
</li>
<li>Configure the preset—please look at the next section for instructions. This is required for the preset to work!</li>
</ol>
<p>What BetterTouchTool should look like after importing the preset, minus the red entries which are private widgets:</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/bettertouchtool-preset-preview.png"><img src="https://andrewchidden.com/content/images/2018/08/bettertouchtool-preset-preview.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<h2 id="configuration">Configuration</h2>
<p>For complete environment variable documentation, please see the README at <a href="https://github.com/andrewchidden/btt-presets">andrewchidden/btt-presets</a>.</p>
<p><strong>Note:</strong> To set some environment variable <code>&lt;env_var&gt;</code> to value <code>&lt;value&gt;</code>, modify (or create) the file <code>~/.bash_profile</code> to include the line <code>export &lt;env_var&gt;=&lt;value&gt;</code>.</p>
<ol>
<li>Identify where you downloaded the controller scripts. Ideally they already have the path <code>~/bettertouchtool</code>.
<ul>
<li>If this is not the case, set the environment variable <code>BTT_USR_ROOT</code> to the controllers directory.</li>
</ul>
</li>
<li>Set the environment variables <code>BTT_WEBSERVER_URL</code> and <code>BTT_WEBSERVER_SHAREDSECRET</code> corresponding to the BetterTouchTool web server URL and shared secret. Can be found by enabling “Advanced” mode, opening “Advanced Settings,” and then navigating to the “Webserver” tab.
<ul>
<li>The URL should be in the format <code>protocol://address:port</code> and will default to <code>http://127.0.0.1:64875</code> if not specified.</li>
</ul>
</li>
<li>Set the environment variable <code>BTT_EVENTKIT_CALENDAR_NAMES</code> to a comma-delimited, case-sensitive list of calendar names that should be checked for upcoming events.</li>
<li>Set the environment variable <code>BTT_GIT_WORKING_DIR</code> to the primary working directory for Git diff statistics and macro-triggered Git operations.</li>
<li>If necessary, modify the “General Touch Bar Settings” to match those below:</li>
</ol>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-settings-1.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-settings-1.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<h2 id="source">Source</h2>
<p>The project is separated into three repositories:</p>
<ol>
<li><a href="https://github.com/andrewchidden/btt-presets"><strong>btt-presets</strong></a> contains the preset JSON configurations.</li>
<li><a href="https://github.com/andrewchidden/btt-controllers"><strong>btt-controllers</strong></a> contains the required controllers and pre-built services for the preset.</li>
<li><a href="https://github.com/andrewchidden/btt-services"><strong>btt-services</strong></a> contains the source of services used by the controllers.</li>
</ol>
<p>The setup loosely follows the model-view-controller pattern, with the services as the model and presets as the view.</p>
<p>Both services and controllers employ good test coverage with <a href="https://github.com/jonreid/OCMockito">OCMockito</a> used to perform unit tests on services and <a href="https://github.com/sstephenson/bats">Bats</a> used to perform unit and integration tests on controllers.</p>
<p>Contributions are welcomed :)</p>
<hr>
<h1 id="optimizingthehigforpowerusers">Optimizing the HIG for Power Users</h1>
<h2 id="regions">Regions</h2>
<p>Apple defines three regions in the <a href="https://developer.apple.com/design/human-interface-guidelines/macos/touch-bar/touch-bar-visual-design/#touch-bar-regions">HIG for the Touch Bar</a>: the system button, app region, and Control Strip:</p>
<p><a href="https://developer.apple.com/design/human-interface-guidelines/macos/images/TB_layout_regions_2x.png"><img src="https://developer.apple.com/design/human-interface-guidelines/macos/images/TB_layout_regions_2x.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<p>I instead follow these guidelines to better suit my own workflow:</p>
<p><strong>The system button</strong> should <em>always</em> show the Escape button. Apple’s guidelines say to alter the button based on context, such as showing Cancel or Done, but a static Escape key seems much more suitable for power users given its frequency of usage.</p>
<p><strong>The app region</strong> should show current workflow state as text. Controls in the app region offer utility regardless of context, and thus should <em>not</em> change when switching between applications.</p>
<p><strong>The Control Strip</strong> should show a static set of system-level controls such as buttons to adjust the volume or screen brightness.</p>
<h2 id="visualdesign">Visual Design</h2>
<p><a href="https://andrewchidden.com/content/images/2018/08/touchbar-screen-overview-1.png"><img src="https://andrewchidden.com/content/images/2018/08/touchbar-screen-overview-1.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<p>Clearly the design of my preset is not flashy or colorful. However, since the MacBook Pro keyboard doesn’t come with a multi-colored LED backlight, it seems logical that the Touch Bar should follow suit. Even Apple’s guidelines suggest that developers “make the Touch Bar similar in appearance to the physical keyboard” by using monochromatic colors.</p>
<p>The spartan design doesn’t just match the surrounding hardware better, but creates fewer distractions while working. And because the layout doesn’t change when switching between applications, it’s also possible to press the correct control without looking.</p>
<h4 id="buttons">Buttons</h4>
<p>Although the visual design roughly follows Apple’s HIG, the largest difference comes from the treatment of buttons. From a personal standpoint, the gray bezel simply looks out of place when applied to all button types. Apple comments that “the system-provided bezel is designed to have an appearance similar to physical keyboard buttons.” But the keys on space gray MacBook Pro models are <em>black</em>, not gray.</p>
<p>While it’s not possible to match the appearance of physical keys on the Touch Bar, one alternative is to simply reduce the number of buttons with gray backgrounds. I designed my setup around the following button types:</p>
<p><strong>Icon-only button:</strong> No background. The icon already indicates the control’s interactivity.</p>
<p><strong>Tappable text label:</strong> No background. The label’s primary function is to display a textual status, not act as an interactive control.</p>
<p><strong>Normal text button:</strong> Gray background. The button’s primary function is to respond to taps and perform an action.</p>
<p><strong>Destructive text button:</strong> Red background. Similar to a normal text button, but performs a destructive and potentially undoable action.</p>
<h2 id="anatomyofausefulcontrol">Anatomy of a Useful Control</h2>
<p>The set of controls to show is a deeply personal choice that depends on workflow and preference. These are the guidelines I followed when creating my Touch Bar controls:</p>
<p><strong>Avoid controls whose primary action is to launch an application.</strong> I found that the Touch Bar could not adequately replace the Dock or Spotlight. Instead of application icons, I use tappable text labels that can show workflow status in addition to opening the relevant application.</p>
<p><strong>Offer functionality beyond what a single keyboard shortcut can accomplish.</strong> To maximize utility I only assign actions to buttons that trigger non-trivial workflow macros, such as pulling and then rebasing.</p>
<p><strong>Discard any controls not workflow related.</strong> Although at one point I considered widgets to show the weather and AAPL stock price, neither directly affect my development workflow.</p>
<p><strong>Handle modifier keys and long presses for surfacing complementary actions.</strong> As a power user, I’m used to taking advantage of macOS features hidden behind modifier keys. I wanted to extend similar functionality to the Touch Bar such as by locking the screen or muting the volume when long pressing.</p>
<p><strong>Use buttons or swipe gestures instead of sliders.</strong> Sliders take up valuable space where other controls could fit into. Discrete buttons and horizontal swipe gestures can work similarly well, but without the spacial overhead.</p>
<hr>
<h1 id="bettertouchtooldevelopmenttips">BetterTouchTool Development Tips</h1>
<p>The complexity of BetterTouchTool can make it a difficult platform to work with. I’ve compiled some tips and best practices for those thinking of developing with BTT.</p>
<h2 id="usepixelalignedicons">Use Pixel-Aligned Icons</h2>
<p>To achieve best results, only include pixel-aligned icons with a scale factor of @2x. <a href="https://developer.apple.com/design/human-interface-guidelines/macos/touch-bar/touch-bar-icons-and-images/#system-provided-icons%5D">Apple’s set of system-provided icons</a> usually work well as a starting point.</p>
<p>In some cases, you may need to create custom icons or pixel-align existing ones. For example, the brightness up and down icons in my setup are improved pixel-aligned re-creations of the system ones. See also <a href="https://developer.apple.com/design/human-interface-guidelines/macos/touch-bar/touch-bar-icons-and-images/#custom-icons">Apple’s guidelines on custom icons</a>.</p>
<p>BTT will not automatically render icons at the @2x scale factor, so you must specify the icon width and height in the item customization options. Note that BTT accepts icons dimensions in <em>points</em> rather than in <em>pixels</em>, contrary to what’s shown next to the text box.</p>
<h2 id="optimizeincludedassets">Optimize Included Assets</h2>
<p>On the topic of icons, consider performing lossless compression on icons and other image assets. Doing so makes the exported JSON much smaller (obviously) but also more readable when shared. Smaller icons also result in better rendering performance, although most gains will come from providing correctly-scaled, pixel-aligned icons.</p>
<p>Free tools such as <a href="https://imageoptim.com/mac">ImageOptim</a> are straightforward to use and often reduce icon sizes by over 50%.</p>
<p><a href="https://andrewchidden.com/content/images/2018/08/imageoptim-multi.png"><img src="https://andrewchidden.com/content/images/2018/08/imageoptim-multi.png" alt="The MacBook Pro is Dead. Long Live the MacBook Pro with Touch Bar."></a></p>
<h2 id="addtestautomationforcustomcontrols">Add Test Automation for Custom Controls</h2>
<p>A wise man once said “you can never have too many tests.” While clearly this isn’t true and perhaps the man wasn’t so wise, we do want some way other than the Touch Bar breaking during the day to verify that things work as expected.</p>
<h2 id="preferpushtofetch">Prefer Push to Fetch</h2>
<p>It’s often very inefficient to poll a scripting API just to monitor for state changes. Consider using or creating a push-based service similar to those used in the calendar and volume widgets described above.</p>
<h2 id="minimizeapplescriptusage">Minimize AppleScript Usage</h2>
<p>AppleScript is not known for its performance. BTT officially gained support for direct shell script invocation in version 2.331 (released Jan 20, 2018). If the widget doesn’t require interacting with special scripting APIs, consider writing a shell script or even a standalone command-line utility instead.</p>
<p>In addition, shell scripts can be an order of magnitude faster. Since scripts may run often if used for polling an API, using AppleScript incurs a non-negligible energy and performance overhead. From the BTT developer:</p>
<blockquote>
<p><strong>“Run Shell Script and Show Return Value”</strong> In many scenarios this will be much more performant than the Apple Script widget as it can run multiple scripts/tasks/commnands[sic] in parallel. It can be combined with the new predefined action “Execute Shell Script / Task”.</p>
</blockquote>
<h2 id="monitorenergyimpact">Monitor Energy Impact</h2>
<p>You can use Activity Monitor check the energy impact of both BetterTouchTool and its children. While it doesn’t show more detailed instrumentation or individual script performance, it does provide a good benchmark.</p>
<p>See also <a href="https://blog.mozilla.org/nnethercote/2015/08/26/what-does-the-os-x-activity-monitors-energy-impact-actually-measure/">Nicholas Nethercote’s post</a> on what exactly is behind the “energy impact” value in Activity Monitor.</p>
<h2 id="aimforfeatureparitywithnativecontrols">Aim for Feature-Parity with Native Controls</h2>
<p>If replacing items in the Control Strip, handling modifier keys and key repeating can make presets feel less divorced from the native system controls. Power users especially may expect certain key combinations to work in particular ways, such as shift + volume playing a feedback sound.</p>
<h2 id="jointhebettertouchtoolcommunity">Join the BetterTouchTool Community</h2>
<p>There’s a relatively small bunch of BetterTouchTool users and developers on the <a href="https://community.folivora.ai/">official BTT forum</a>. It can provide a source of inspiration for new widgets or even basic code samples. And if all else fails, ask Andreas Hegenberg (the developer of BTT) for support. He’s quite responsive to questions on the forum.</p>
<hr>
<h1 id="conclusion">Conclusion</h1>
<p>With any luck you’re a little more convinced that the Touch Bar actually can be useful for power users. It just takes a few software modifications and a little creativity. ♦</p>
<p>Comments and discussions:</p>
<ul>
<li><a href="https://old.reddit.com/r/apple/comments/99f2ks/the_macbook_pro_is_dead_long_live_the_macbook_pro/">Apple subreddit post</a></li>
<li><a href="https://news.ycombinator.com/item?id=17812724">Hacker News post</a></li>
<li><a href="https://community.folivora.ai/t/a-standard-set-of-responsive-energy-efficient-widgets-volume-brightness-calendar-timer-world-clock-git/3083">BetterTouchTool forum thread</a></li>
<li><a href="http://andrewchidden.com/contact/">Email me</a></li>
</ul>
</div>]]></content:encoded></item><item><title><![CDATA[Defeating Spam Callers With Speech Recognition and Call Forwarding]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Every day at 11:00 AM my iPhone rings. With the “seamless” “magic” of Apple’s <a href="https://support.apple.com/en-us/HT204681">Continuity</a>, the ringing quickly spreads to my personal MacBook, work MacBook, iPad, and Apple Watch.</p>
<p><a href="http://andrewchidden.com/content/images/2017/12/spammity-spammer.PNG"><img src="http://andrewchidden.com/content/images/2017/12/spammity-spammer.PNG" alt="spammity-spammer"></a></p>
<p>“This is your last warning regarding your car insurance…”</p>
<p>I don’t own a car. I wouldn’t mind</p></div>]]></description><link>http://andrewchidden.com/defeating-spam-callers-with-speech-recognition-and-call-forwarding/</link><guid isPermaLink="false">5a3e1cf65c963a0722d2ccfe</guid><category><![CDATA[Projects]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Andrew Chidden]]></dc:creator><pubDate>Mon, 25 Dec 2017 02:11:46 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>Every day at 11:00 AM my iPhone rings. With the “seamless” “magic” of Apple’s <a href="https://support.apple.com/en-us/HT204681">Continuity</a>, the ringing quickly spreads to my personal MacBook, work MacBook, iPad, and Apple Watch.</p>
<p><a href="http://andrewchidden.com/content/images/2017/12/spammity-spammer.PNG"><img src="http://andrewchidden.com/content/images/2017/12/spammity-spammer.PNG" alt="spammity-spammer"></a></p>
<p>“This is your last warning regarding your car insurance…”</p>
<p>I don’t own a car. I wouldn’t mind if this happened once or twice a month, but I’m receiving upwards of two spam calls per day. It holds my devices hostage until I decline the call.</p>
<p>I first tried the most obvious solution: caller ID-based blocking. Apple introduced CallKit in iOS 10, allowing apps to blacklist up to 2,000,000 numbers<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>. I downloaded the two most popular call blocking apps on the App Store to test the method’s efficacy: Truecaller and Mr. Number. However, neither could identify (much less block) more than 10% of the spam calls I previously received<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>. Quite disappointing.</p>
<p>Since using commercial off-the-shelf solutions wasn’t an option, I had to look elsewhere. A few days ago I started working on my first attempt to stop the deluge of spam calls. I call it <strong>Carbon Call</strong>.</p>
<h2 id="carboncall">Carbon Call</h2>
<p>The system acts as a kind of virtual receptionist. It plays a pre-recorded prompt phrase for callers and then validates their answer using speech recognition on a set of keywords. For example:</p>
<blockquote>
<p><strong>Prompt:</strong> “Please provide the name of the person you are trying to reach.”<br>
<strong>Valid keywords:</strong> “Andrew” and “Chidden”</p>
</blockquote>
<p>Adjusting the prompt and keywords allows the system to block anything from only robocalls to people who dislike <em>Star Trek</em>. For callers providing caller ID, the system automatically white-lists their number if they pass the test<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>.</p>
<p>Once the system verifies that the caller isn’t a spammer, my phone rings and I’m connected in under ten seconds. If I’m not available to answer the call, Carbon Call also handles voicemail recording and notifications.</p>
<p>It’s quite simple but works remarkably well. In the past two days I haven’t received a single spam call. I put together a short audio demo showing the call flow<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup>:</p>
<center>
    <audio controls>
        <source src="http://andrewchidden.com/content/images/2017/12/demo-sample.mp3" type="audio/mp3">
        Your browser cannot play this audio clip.
    </audio>
</center>
<h4 id="features">Features</h4>
<p>Carbon Call needed to meet very specific requirements which shaped how I designed the system. These are the current marquee features:</p>
<ol>
<li><strong>Uses my existing phone number.</strong> Actual, non-spammer humans call my current number, so switching to a new number isn’t an option.</li>
<li><strong>Free.</strong> There’s some utility in blocking spam calls, but I want to avoid paying another monthly subscription and calling rate. Carbon Call costs nothing per month or per minute<sup class="footnote-ref"><a href="#fn5" id="fnref5">[5]</a></sup>.</li>
<li><strong>Works without cellular data.</strong> I’m usually inside 4G or LTE coverage and have high monthly data limits. However, the quality of service for Voice over IP (VoIP) can degrade on 2G and 3G connections<sup class="footnote-ref"><a href="#fn6" id="fnref6">[6]</a></sup>.</li>
<li><strong>Low false positive and negative rate.</strong> This is a given for any effective spam-filtering system. Carbon Call filters out robocalls and general spam without blocking legitimate callers.</li>
<li><strong>Integrates with the native iOS phone app.</strong> I don’t want to use or maintain a 3rd party call history interface. Calls received behind Carbon Call show up in the phone app’s <em>Recents</em> tab.</li>
</ol>
<h4 id="gettingcarboncall">Getting Carbon Call</h4>
<p>For the proof of concept, I specifically designed the system to accommodate a single user: me. Depending on how much interest there is in Carbon Call, I’ll consider either expanding and optimizing the project or just open sourcing it.</p>
<p>I’ll make a post on this site when I come to a conclusion on how to proceed. I suggest <a href="http://andrewchidden.com/subscribe/">subscribing</a>, or you can just check back in a few weeks.</p>
<h2 id="technicalimplementation">Technical Implementation</h2>
<p>In terms of external dependencies, Carbon Call runs on a 1 GB <a href="https://www.linode.com/?r=a70b3a065e338b77b5f722e6ce2d9a9cff2a2c0d">Linode box</a> (referral link), a single Google Voice account, and the Google Cloud Platform Speech API (GCP).</p>
<ul>
<li><strong>Linode</strong> offers more memory per dollar than Digital Ocean (1 GB vs 512 MB). However, I much prefer Digital Ocean’s control panel design.</li>
<li><strong>Google Voice</strong> hides a lot of the complexities inherent in VoIP, which is good for development but not for commercial or enterprise use<sup class="footnote-ref"><a href="#fn7" id="fnref7">[7]</a></sup>. That said, Google Voice offers free domestic calls in the United States along with iOS and web client integrations. Why pay for SIP trunking and a virtual number when you can just sell your soul to Google’s ad machine? :)</li>
<li><strong>Google Cloud Platform Speech API</strong> gives decently accurate results without much work. The relatively short and infrequent recordings make the free tier offered by GCP more than adequate<sup class="footnote-ref"><a href="#fn8" id="fnref8">[8]</a></sup>.</li>
</ul>
<p>To understand the architecture, we can trace the path of a Carbon Call-protected conversation between Alice and Bob. Alice is calling Bob’s iPhone.</p>
<ol>
<li>Alice dials Bob’s phone number. Bob has enabled <a href="https://support.apple.com/en-us/HT202176">call forwarding</a> on his iPhone so that all incoming calls forward to his Google Voice number.</li>
<li>The Carbon Call server continuously monitors for incoming calls on Bob’s Google Voice account and answers the forwarded call from Alice.</li>
<li>Alice hears a greeting and Bob’s prompt. If Alice responds <em>incorrectly</em> to the prompt twice then the server kicks her out of the call.</li>
<li>Assuming Alice responds <em>correctly</em> to Bob’s prompt, the Carbon Call server sends a push notification to Bob’s iPhone which rings and prompts Bob to join the ongoing call by dialing his Google Voice number.</li>
<li>Assuming Bob joins the call, the Carbon Call server merges both Alice and Bob’s audio sessions into a single call.</li>
</ol>
<p><a href="http://andrewchidden.com/content/images/2017/12/architecture-flow.png"><img src="http://andrewchidden.com/content/images/2017/12/architecture-flow.png" alt="architecture-flow"></a></p>
<p>Let’s look at the implementation of each step:</p>
<h4 id="1callforwarding">1. Call Forwarding</h4>
<p>Call forwarding from the iPhone to Google Voice allows Bob to retain his old number. Most VoIP services do call forwarding in the reverse direction, forwarding calls received by the VoIP service to your iPhone. For iPhones on GSM networks<sup class="footnote-ref"><a href="#fn9" id="fnref9">[9]</a></sup>, iOS conveniently has a graphical interface in Settings &gt; Phone &gt; Call Forwarding.</p>
<p><a href="http://andrewchidden.com/content/images/2017/12/call-forwarding.png"><img src="http://andrewchidden.com/content/images/2017/12/call-forwarding.png" alt="call-forwarding"></a></p>
<p>When call forwarding is enabled, iOS displays a statusbar icon of a phone with a right-facing arrow.</p>
<h4 id="2callmonitoring">2. Call Monitoring</h4>
<p>Once the call arrives at Google Voice, it propagates to the various audio streaming clients, namely Google Talk and Hangouts. The Carbon Call server uses <a href="http://www.seleniumhq.org/">Selenium</a>, a web automation framework, to headlessly run instances of Hangouts in Google Chrome.</p>
<p>This was the first time I used Selenium, but I found it surprisingly easy to get everything working<sup class="footnote-ref"><a href="#fn10" id="fnref10">[10]</a></sup>. I did encounter a few issues though:</p>
<ul>
<li><strong>Google Accounts</strong> unsurprisingly thought someone hacked my account when I logged in from the server. I needed to install a graphical desktop environment just to get through all of Google’s security prompts.</li>
<li><strong>Google Chrome</strong> leaks memory when continuously run with Selenium’s driver over long periods. I observed roughly 20 MB per hour even when forcing Python’s garbage collection. I’ve resorted to auto-restarting the main Chrome driver every so often to ensure that the server doesn’t hang.</li>
</ul>
<p><a href="http://andrewchidden.com/content/images/2017/12/htop.png"><img src="http://andrewchidden.com/content/images/2017/12/htop.png" alt="htop"></a></p>
<h4 id="3speechrecognition">3. Speech Recognition</h4>
<p>The server plays pre-recorded voice prompts and then records the caller’s response. For speech synthesis, I’m using snippets recorded from Google Translate’s speech output. For speech recognition, I simply send the recordings to GCP’s Speech API to process and return a transcript. A few things I discovered with the Speech API:</p>
<ul>
<li><strong>Silence padding</strong> is required for results. I originally trimmed the leading and trailing silences with <code>sox</code> but wasn’t getting anything back from GCP.</li>
<li><strong>Accuracy</strong> isn’t always the greatest, possibly due to compression over the phone network or little context. The Speech API seems to perform better for longer recordings where it can analyze an entire sentence instead of just a few words. To compensate for poor accuracy, I added phonetic variations to the keywords being matched.</li>
</ul>
<h4 id="4calljoining">4. Call Joining</h4>
<p>Since Bob enabled call forwarding, it’s not possible to directly call his number. The server could bypass Bob’s forwarding rules by initiating a new Hangouts VoIP call, but I want all connections to route over cellular voice.</p>
<p>Instead, Carbon Call uses a custom iOS client able to receive remote push notifications. When Alice passes the tests on the server, Bob receives a push notification causing his iPhone to play a custom vibration pattern and ringtone for thirty seconds.</p>
<p>To accept Alice’s call, the app prompts Bob to call his Google Voice number with a single tap. iOS displays the alert regardless of where Bob is in iOS, including the lock screen.</p>
<p><a href="http://andrewchidden.com/content/images/2017/12/call-alert.png"><img src="http://andrewchidden.com/content/images/2017/12/call-alert.png" alt="call-alert"></a></p>
<h4 id="5audiorouting">5. Audio Routing</h4>
<p>When Bob calls his Google Voice number, the server needs to create a conference call between Alice and Bob’s separate lines. Conference calling <em>should</em> be trivial and accomplished with a built-in option, but Google Voice no longer supports this feature for web-based Hangouts clients<sup class="footnote-ref"><a href="#fn11" id="fnref11">[11]</a></sup>.</p>
<p>Instead, the server initiates two separate Chrome instances (one for Alice and one for Bob), and uses a set of <a href="https://www.freedesktop.org/wiki/Software/PulseAudio/">PulseAudio</a> modules to route the audio between them.</p>
<h2 id="userexperience">User Experience</h2>
<p>There’s a lot that goes on between Alice dialing Bob’s number and Bob joining the call. While I spent the first day on research and development, the second day was spent on streamlining the experience. Small things such as adding two attempts to the caller validation helped tremendously in smoothing out interactions.</p>
<h4 id="holdmusic">Hold Music</h4>
<p>After the system verifies that Alice isn’t a spammer (either through Bob’s prompt or via whitelist), Alice needs to wait a moment before Bob connects. Playing some kind of hold music to keep Alice on the line is an obvious solution, but the specific type of music makes a difference.</p>
<p>Due to compression along the phone network and conversion process from digital to analogue, most music sounds utterly horrible. The Strauss <em>Horn Concerto No. 2</em> becomes the <em>Harmonica Concerto No. 2</em>, and <em>Festive Overture</em> becomes decidedly not festive.</p>
<p>Phone audio compression algorithms optimize for the voice frequency band (300 to 3400 HZ), but music usually covers a much larger range of frequencies. After playing a dozen different tracks over the phone, I finally settled on a modified version of <em>Resignation</em> by Kevin MacLeod. It’s a slow solo piano piece in the middle of the frequency range, making it a good fit for hold music:</p>
<center>
    <audio controls>
        <source src="http://andrewchidden.com/content/images/2017/12/resignation-sample.mp3" type="audio/mp3">
        Your browser cannot play this audio clip.
    </audio>
</center>
<p>Using <a href="http://www.audacityteam.org">Audacity</a>’s frequency analysis tool, we can see the difference between the first thirty seconds of <em>Festive Overture</em> and <em>Resignation</em>'s frequency ranges:</p>
<p><a href="http://andrewchidden.com/content/images/2017/12/festive-freq.png"><img src="http://andrewchidden.com/content/images/2017/12/festive-freq.png" alt="festive-freq"></a></p>
<p><a href="http://andrewchidden.com/content/images/2017/12/resignation-freq.png"><img src="http://andrewchidden.com/content/images/2017/12/resignation-freq.png" alt="resignation-freq"></a></p>
<p>80% of <em>Resignation</em> falls between 300 and 3400 HZ, with minor clipping expected in the bass range (&lt; 300 HZ). In contrast, around 80% of <em>Festive Overture</em> will be clipped or heavily distorted starting from the upper mid-range. My version of <em>Resignation</em> still doesn’t sound the greatest when played over the phone, but it doesn’t make your ears bleed either.</p>
<h2 id="caveats">Caveats</h2>
<p>It’s not all milk and honey in the walled garden of Carbon Call. There’s still a few challenges with the current version of the system:</p>
<ul>
<li><strong>High latency</strong> for calls due to Google Voice’s VoIP implementation. On average, I’m seeing roughly 800ms between speaking into one phone and hearing it on the other. For reference, 150ms is widely considered the maximum acceptable latency for real-time voice applications. In my testing, the latency wasn’t horrible, but certainly less than ideal<sup class="footnote-ref"><a href="#fn12" id="fnref12">[12]</a></sup>.</li>
<li><strong>High memory</strong> requirements to run multiple headless Chrome instances makes it difficult to scale the setup. The box sees about 80% memory pressure during active calls. Adding swap memory isn’t sufficient to maintain adequate performance either.</li>
<li><strong>Brittleness</strong> due to scripting Selenium on the DOM structure. If Google substantially changes the Google Accounts login flow, Hangouts, or the call frame, then things will start breaking in unexpected ways.</li>
</ul>
<p>Solving these problems require building or purchasing access to a custom VoIP network. It’d be a lot of work and essentially a complete re-write. For the time being, I’m satisfied with the current setup as a proof of concept. ♦</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>You can add a new blocked number in a <code>CXCallDirectoryProvider</code> by calling <code>addBlockingEntryWithNextSequentialPhoneNumber:</code> 2,000,000 times before iOS displays an error. However, attempting to save a smaller (but still large) number of entries (e.g. above 1,000,000) causes iOS to show an indefinite activity spinner when enabling the extension. Subsequent attempts to enable or disable the extension silently fail on iOS 11.2, even after restarting the device and re-installing the app. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>I’m guessing most of the numbers were from spoofed caller IDs, making these kinds of apps ineffective for me. <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p>Spammers can spoof caller IDs, but it’s unlikely that they would spoof one of my contacts. Preventing targeted attacks lies outside the scope of the project. <a href="#fnref3" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn4" class="footnote-item"><p>I shortened some sequences for brevity. <a href="#fnref4" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn5" class="footnote-item"><p>Thanks to <a href="https://www.relay.fm/radar">Under the Radar</a>, the Linode box (see the <em>Architecture</em> section of this post) is free for the next four months. <a href="#fnref5" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn6" class="footnote-item"><p>On 2G and 3G, cellular voice calls will use the Public Switched Telephone Network (PSTN), which should have better optimization for voice-only traffic: <a href="http://ieeexplore.ieee.org/document/6148783/">ieeexplore.ieee.org</a>. <a href="#fnref6" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn7" class="footnote-item"><p>I initially wanted to build my own VoIP network using <a href="https://www.asterisk.org/">Asterisk</a>, a popular framework for VoIP gateways. Although setting up a single VoIP server is fairly trivial, connecting it to the real world requires paying for a SIP trunk and virtual phone number (a Direct Inward Dialing number). Trustworthy SIP trunking services charge both a monthly fee and inbound / outbound rate. Once I factored in the virtual server costs needed to run an Asterisk instance, the final amount came out to over $10 a month — way past my allocated budget. <a href="#fnref7" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn8" class="footnote-item"><p>I’ve previously used <a href="https://cmusphinx.github.io/">CMUSphinx</a> for offline speech recognition, but GCP is much easier to use and a better fit for Carbon Call. <a href="#fnref8" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn9" class="footnote-item"><p>Apple’s <a href="https://support.apple.com/en-us/HT202176">support page</a> says to “Contact your carrier for information” regarding call forwarding on CDMA networks. I use T-Mobile (GSM), so your mileage may vary. <a href="#fnref9" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn10" class="footnote-item"><p>I chose to use Python to script the logic and run the Selenium Chrome driver. I usually use Go or Node.js, but my side goal was to learn the basics of Python for this project. <a href="#fnref10" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn11" class="footnote-item"><p>It may be a <a href="https://support.google.com/voice/answer/115083?co=GENIE.Platform%3DDesktop&amp;hl=en&amp;oco=0">documented feature</a>, but conference calling for Google Voice only works for forwarded calls (out of Google Voice) and when “in-bound call options” are enabled. <a href="#fnref11" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn12" class="footnote-item"><p>Some profiling and research shows that the problem lies with Google Voice. It takes around 400ms for audio to go from the telephone network to Hangouts. For whatever reason, the opposite direction “only” takes around 100ms. <a href="#fnref12" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
</div>]]></content:encoded></item></channel></rss>