Building the Womack Audio Suite: note-aware resonance, a fisheye EQ, and a bus that almost wasn't

I set out to build a small family of macOS audio plugins that feel like they were made by the same hand and know about each other: a note-aware resonant filter, a fuzz/vibe/delay pedalboard, a lyric teleprompter — and, underneath them, a shared “bus” so instances can talk. Along the way I fought a filter into warmth without letting it scream, drew an EQ curve through a fisheye lens, threw out a chord detector that couldn’t name basic chords, and spent an afternoon learning why a plugin can work perfectly in Standalone and be stone dead in Logic.

This is the engineering story — the musical decisions and the programming ones — with the war stories left in.

The suite today:

  • Womack FX — a fuzz → univibe → tape-delay pedalboard (aufx).
  • Womack Resonote — a note-quantized resonant filter/EQ, single- and multi-band (aumf).
  • Womack Lyriqueue — session-stored lyrics with a playhead-following teleprompter (aufx).
  • WomackBus — a shared, in-process message bus the plugins register on.

Stack throughout: JUCE 8.0.12, C++20, CMake (Xcode generator), building AU + VST3 + Standalone, unit-tested with JUCE’s console UnitTest runner, signed and notarized for distribution.

Suite architecture: Womack FX, two Resonote instances, and Lyriqueue all registering on one shared WomackBus dylib inside a single DAW process


Part 1 — Resonote: making a filter that knows about notes

Most EQs think in raw Hz. Musicians think in notes. Resonote closes that gap: its cutoff can snap to note frequencies, optionally constrained to a key, with a live cents-from-nearest readout so you always know how in-tune the resonance is.

Note math you can trust

The pitch math lives in a small, pure, unit-tested header (NoteFrequency.h): MIDI↔Hz, nearestMidi, noteName, centsFromNearest, and scale-aware snapping for chromatic, major, and minor. Keeping it pure meant I could test the tricky parts directly instead of poking at a filter and squinting at a spectrum.

One bug from this corner is worth calling out because it’s so ordinary: the cents readout once showed +9.61706e-05 c. juce::String(cents, 0) doesn’t round — it formats — so a whisker above zero printed in scientific notation. The fix was a juce::roundToInt before display. Tiny, but it’s the difference between “precise instrument” and “toy.”

The warmth: one SVF with a tanh in the feedback

The heart of Resonote is a single zero-delay-feedback TPT state-variable filter (ResonantSVF). It runs in Bell, Low-Pass, or High-Pass mode. The only “analog” trick — and it’s deliberately the only one — is a tanh nonlinearity in the resonance feedback path.

Resonote SVF signal path: input summed through two TPT integrators to bandpass and lowpass, with a tanh of the bandpass fed back as negative resonance

That tanh does two jobs. It adds a gentle, level-dependent saturation as resonance climbs — the “warmth” — and it self-limits, so the filter stays stable and never tips into self-oscillation. That let me be aggressive with the resonance range. resonanceToQ maps a normalized 0..1 knob to Q ≈ 0.5 … 75:

1
2
3
4
5
6
// 0..1 -> Q 0.5..~75, musically weighted toward the top of the range.
float resonanceToQ (float r) noexcept
{
r = juce::jlimit (0.0f, 1.0f, r);
return 0.5f + std::pow (r, 3.0f) * 74.5f;
}

Cranked, it sings on a note without ever howling — because the tanh in the loop eats the runaway energy that self-oscillation would need.

The fisheye response curve

An EQ curve on a log-frequency axis wastes its most interesting real estate: the region right around where you’re working is cramped. So Resonote’s response display bends space. It draws everything through a horizontal fisheye — an erf “bump” that magnifies the area around the current cutoff and compresses the far edges — plus a subtle convex vertical bow for a tactile “glass” feel.

Schematic of the fisheye response curve

Schematic illustration (not a screenshot).

The horizontal mapping from a normalized log-frequency position u ∈ [0,1] to a pixel x is:

1
2
3
4
5
6
// erf "bump" centred on u0 (the cutoff), pinned at the frame edges.
float uToX (float u) const noexcept
{
const float bump = lensStrength * (std::erf ((u - lensU0) / lensWidth) - lensBumpAt0);
return lastArea.getX() + ((u + bump) / lensDenom) * lastArea.getWidth();
}

Because uToX is monotonic increasing in u, it has a well-defined inverse — and I need that inverse constantly: to hit-test the mouse against the curve, and to let you drag the crossover handles between bands and have them land exactly on the boundary they control. Rather than derive a closed-form inverse of an erf, I just bisect:

1
2
3
4
5
6
7
8
9
10
11
12
// uToX is monotonic in u, so bisect for its inverse. ~30 iterations over [0,1]
// is well below sub-pixel at any realistic width.
float xToU (float x) const noexcept
{
float lo = 0.0f, hi = 1.0f;
for (int i = 0; i < 30; ++i)
{
const float mid = 0.5f * (lo + hi);
if (uToX (mid) < x) lo = mid; else hi = mid;
}
return 0.5f * (lo + hi);
}

Thirty iterations is nothing per frame, it’s trivially correct, and it means the exact same warp is used for drawing and for interaction. The handles never drift off the slabs they sit on.

Multiband: up to four bells that don’t step on each other

Resonote grew from one band to up to four, each a colour-coded bell with its own frequency, resonance, and gain, and each confined to a mutually-exclusive range at least an octave wide. A + / – LED changes the band count; adding a band auto-splits the spectrum, after which the crossovers are draggable (through that same fisheye inverse). Each band also gets tempo-synced resonance modulation — an LFO with a per-band depth and a host-synced rate, plus a global shape.

Schematic of the multiband layout

Schematic illustration (not a screenshot).

The chord detector I had to rip out and rewrite

The multiband version reads out the chord formed by the active bands. My first attempt was hand-rolled, and it was simply wrong: for the notes of an F minor 7 it displayed F +3 +5 +7 — printing raw semitone offsets as if they were chord degrees. For a music app, that’s just not good enough.

There is no clean drop-in C++ chord library, so I ported the algorithm that the JavaScript world trusts — tonal.js-style detection: normalize the pitch classes, then rotate through every note as a candidate root, match the resulting interval set against known chord qualities, and prefer the interpretation with the best root; fall back to slash-chord inversions (C/E) and, only if nothing matches, to an honest note-name list rather than fake degrees. Augmented-7 spells as aug7, never +7, precisely because a + had been the symptom of the old bug.

The lesson wasn’t “chords are hard” (they are). It was: when your domain has a correct answer, either compute the correct answer or say you can’t — don’t invent notation that looks right.


Part 2 — The Logic bug class that bit us three times

Here’s the war story every plugin dev should internalize.

Resonote’s visualizer, note readout, and chord display all read their values from small std::atomic snapshots that were written inside processBlock. In the Standalone app that was fine — the audio device runs continuously, so processBlock fires forever and the snapshots are always fresh. Ship the same binary into Logic, stop the transport, turn a knob… and nothing moves. The curve freezes. The chord goes stale. It looked broken, and only in the DAW.

Stale-snapshot bug: the UI reading process-time snapshot atomics goes live in Standalone but stale in Logic when the transport is stopped; the fix reads the parameter directly

The root cause: Logic doesn’t call processBlock when the transport is stopped (and only does at all when audio flows through the track). Reading UI values from process-time snapshots couples your interface to the transport state. The fix is a one-liner in spirit — read the parameters directly on the message thread, not the process-time mirror — and I routed every UI getter through a single effectiveBandFreqHz() helper so the audio path and the display can never disagree.

It bit three times: first the band count stuck at 1, then the whole visualizer + chord readout went inert, then Lyriqueue’s transport readout froze for the same reason. “Works in Standalone, dead in Logic” is now a smell I recognize instantly.

The sibling gotcha: change gestures

While chasing the band-count issue I hit its cousin. Writing a parameter from code with setValueNotifyingHost works in Standalone and silently reverts in Logic — unless you bracket it in a gesture:

1
2
3
param->beginChangeGesture();
param->setValueNotifyingHost (newValue);
param->endChangeGesture();

Logic treats an un-gestured programmatic write as noise and rolls it back. Standalone doesn’t care. Same shape of bug, same “only in the DAW” signature.

The meta-lesson: Standalone is a liar of omission. It’s a great fast loop, but the DAW is the only ground truth. auval on every build, then quit-and-reopen Logic (it caches AU binaries in memory) before you trust anything.


Part 3 — Lyriqueue: lyrics that follow the playhead

Lyriqueue stores lyrics in the session and shows a teleprompter that follows the DAW’s playhead, cue by cue, in musical time. Cues are stored canonically as absolute PPQ (quarter-notes from song start) and rendered as Bar.Beat.Ticks using the host’s time signature. The active line is simply “the cued line with the greatest cue ≤ now.”

An early version showed impossible Bar.Beat.Ticks values; the fix was to do all arithmetic in PPQ and convert once at the edges, so beats and ticks always roll over musically.

It’s an aufx audio effect that doesn’t touch the audio — which produced a genuinely confusing support moment: on a silent track, Logic never processes it, so the playhead never arrives and following looks broken. The right move wasn’t code gymnastics; it was discoverability — a live transport readout plus a “waiting for transport” hint that appears only when the host has never processed the track (detected with a processBlock counter that never advances), and latches off the instant a block arrives so it never nags on a working track.


Part 4 — WomackBus: the shared bus that almost wasn’t

This is the part I’m proudest of, because it looked done, passed its tests, and was quietly wrong.

I wanted the plugins to be a suite — to share musical context in one process. So I built WomackBus: a process-wide singleton with a client registry, a small blackboard, and change listeners; each plugin owns a RAII WomackBusClient that registers on construction and unregisters on destruction, and shows a tiny “Womack ×N” presence badge.

I built WomackCommon as a static library, linked it into each plugin, saw the badge show ×1, shipped it. The unit tests were green. The badge was… always ×1 across different plugins.

Why static linking silently broke it

A function-local-static singleton (WomackBus::get()) linked statically into two separate plugin bundles gets duplicated — each bundle carries its own copy. On macOS with the default two-level namespace, strong duplicate symbols are not coalesced across separately loaded bundles. So Resonote had one bus, Lyriqueue had another, and they never saw each other. Two instances of the same plugin shared (same image); two different plugins did not.

nm shows it plainly — every plugin binary defined the symbol (T), instead of importing it:

1
2
3
4
$ nm "Womack Resonote" | grep WomackBus3getEv
0000000000009ea0 (__TEXT,__text) external __ZN9WomackBus3getEv # T: its OWN copy
$ nm "Womack Lyriqueue" | grep WomackBus3getEv
0000000000009094 (__TEXT,__text) external __ZN9WomackBus3getEv # T: a DIFFERENT own copy

Static lib gives each plugin bundle its own WomackBus copy (two buses); one shared dylib gives every plugin an imported symbol and a single bus per process

The fix: one shared, JUCE-free dylib

WomackCommon became a single shared dylib with an absolute install name under a shared directory (/Library/Application Support/Womack for installs; ~/Library/Application Support/Womack for dev). Every plugin records that one path, so at load time dyld maps one copy — one bus per process. Now nm shows the symbol imported (U) everywhere:

1
2
3
4
$ nm "Womack Resonote" | grep WomackBus3getEv
U __ZN9WomackBus3getEv # imported from the shared dylib
$ otool -L "Womack Resonote" | grep Womack
/Library/Application Support/Womack/libWomackCommon.dylib

There was one more trap I designed around from the start of the rewrite: the dylib’s ABI is deliberately JUCE-freestd::string, a plain std::uint32_t ARGB, plain structs. Why? Each plugin already embeds its own JUCE runtime. If the shared dylib also linked JUCE and I passed, say, a juce::Identifier or juce::String across the boundary, I’d be sharing objects that reference JUCE’s global string pool — but there’d be two JUCE runtimes with two pools. That’s a corruption waiting to happen. Keeping the bus boundary to std types means it links only libc++/libSystem (verifiable with otool -L) and can’t entangle the runtimes. Plugins convert juce::String ↔ std::string at the edge.

The installer ships the dylib as a required component (always installed), Developer-ID signed and notarized alongside the plugins.

The takeaway: green tests are necessary, not sufficient. The bus’s logic was always correct — N clients produce count N. What was wrong lived in the linking, a layer the unit tests couldn’t see. nm/otool were the tests that mattered here.


Part 5 — Where it’s going: shared musical context

With one real bus in place, the suite can finally share music, not just presence. The next piece (in progress) adds two things over the bus, both opt-in, both symmetric-peer:

  1. Shared key — turn on “Sync Key” and changing root/scale in one instance updates the others (with origin-id + value-compare + an “adopting” guard so it can’t echo forever).
  2. Frequency-signature awareness — each Resonote publishes the frequencies it occupies, and every instance can see the others’ occupied frequencies as coloured ghosts on its own curve, with a warning when two land within 50 cents of each other. Put a Resonote on a piano and another on an organ, and each can carve out its own spectral pocket instead of fighting for the same one. Awareness first; you carve manually; automatic avoidance is on the backlog.

Frequency-signature sharing: Resonote on piano and Resonote on organ each publish their signature to WomackBus and see the other's frequencies as ghosts with overlap warnings

A future Womack Ex Machina on the master bus can then become the leader that drives the whole suite — but that’s a later chapter.


How it’s built

A few process notes, because they mattered as much as the DSP:

  • Worktree per task. Every change happens on its own git worktree/branch; main only ever receives tested, confirmed work. Multiple build sessions never collide.
  • TDD on the pure parts. Note math, band ranges, chord naming, and the sync helpers are all pure and unit-tested (ResonoteTests, WomackBusTests) before they touch a filter or a UI.
  • Subagent-driven development. Larger features are executed task-by-task with a fresh implementer per task and a spec + code-quality review after each — plus a broad review before merge.
  • Validate relentlessly. auval on every build for all three plugins; nm/otool for the linking guarantees; quit-and-reopen Logic before trusting a change.

Development workflow: worktree and branch, failing test, implement, auval plus unit tests, task review, then merge to main with --no-ff


Closing

The fun of this project has been that the musical problems and the systems problems keep turning out to be the same problem viewed from two sides. A filter that “knows about notes” is equal parts pitch math and a fisheye you can drag. A “suite” is equal parts a shared key and a symbol that must be U and not T. And the recurring humbling lesson — from the scientific- notation cents, to the F +3 +5 +7 chord, to the bus that passed its tests while being wrong — is that looking right is not the same as being right. The DAW, the linker, and the music theory all get a vote.

More soon, once instances can carve out their own place in the mix.

— Womack Audio


Experimenting with the experimental in Node 20 and beyond

I took 30m to experiment with Node v20 yesterday, initially focused on experimental permissions. I noticed features that have been introduced since Node 14 that apply to Sports Card Investor’s engineering org, e.g. corepack.

Node/CorePack

One of those is Node’s corepack, which holds pnpm as an add’l official package manager that it will manage for you. You enable corepack like so. You declare which package manager and version thereof your Node project is using like so

Node::Test

Another is the introduction of the Node-native test package, which works really nicely with assert. assert has always been useful for Node projects but the introduction of test makes it so that jest is just not necessary for many projects

Experimental Permissions

Node 20 now has a method for locking down which system aspects your code can access & have side effects on. I’m predicting that this feature will also be useful for debugging issues related to third-party code interacting with the file system (e.g. “what the hell is mutating this file?”). Here is an example of making FS permissions read-only and limited to the current directory & its descendents. Experimental Node API usage triggers warning messages by default, I’ve also used an environment variable to silence those—the point of that repo is using experimental APIs

Tying it all together

By running these tests with pnpm I’m combining corepack, test & --experimental-permissions to verify that FS reads are enabled in the project directory, while FS writes are not


A modern Node.js 'cluster' example

An example of running cluster on Node 14, including w/ ESM & semantic updates for ES2020

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import cluster from 'cluster';
import {createServer} from 'http';
import {cpus} from 'os';

const isMainCluster = cluster.isMaster;
const {length:CPUCount} = cpus();

if (isMainCluster) {
console.log(`Main ${process.pid} is running`);

// Fork workers.
for (let currentCPU = 0; currentCPU < CPUCount; currentCPU++)
cluster.fork();

cluster.on('exit', worker =>
console.log(`worker ${worker.process.pid} died`));
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);

console.log(`Worker ${process.pid} started`);
}

Changes from the current Node.js ‘cluster’ docs include:

  1. Using import over require
  2. Using named imports over the default where feasible
  3. Using clearer, more semantic identifier names, e.g. currentCPU v. i
  4. Replacing archaic terminology, e.g. Master w/ Main

Output w/ ESM Node code w/ syntactical & semantic updates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> $ node index.mjs                                                                       ⬡ 14.15.1 [±main ●]
Main 57290 is running
Worker 57292 started
Worker 57295 started
Worker 57291 started
Worker 57294 started
Worker 57293 started
Worker 57296 started
Worker 57298 started
Worker 57297 started
Worker 57299 started
Worker 57300 started
Worker 57302 started
Worker 57301 started

See here for the full repo


Finding unDRY code w/ jsinspect

One of the constants in my software dev career has been a predilection for reducing redundant code as much as feasible. There’s a JSX-fluent CLI tool that been helping me do that at iTRVL.

Running npx jsinspect ./packages/agent/src/components/Itinerary/EditItinerary/*Step.js will give you a set outputs like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
./packages/agent/src/components/Itinerary/EditItinerary/FlightStep.js:132,136
<EditableInput
textValue={formatMoney(step.sell, 'USD')}
inputValue={step.sell}
editOnly={true}
inputProps={{ className: classes.editableAlignRight }}

./packages/agent/src/components/Itinerary/EditItinerary/RoadStep.js:136,140
<EditableInput
textValue={formatMoney(step.sell, 'USD')}
inputValue={step.sell}
editOnly={true}
inputProps={{ className: classes.editableAlignRight }}

------------------------------------------------------------

Match - 2 instances

./packages/agent/src/components/Itinerary/EditItinerary/RoadStep.js:94,97
<Paper className="stepLine" elevation={0} style={{ marginLeft: '0px' }}>
<Box className="header">
<Box className="type-icon">
<AirportShuttle />

./packages/agent/src/components/Itinerary/EditItinerary/StayStep.js:284,287
<Paper className="stepLine" elevation={0} style={{ marginLeft: '0px' }}>
<Box className="header">
<Box className="type-icon">
<Tipi />

------------------------------------------------------------

This is especially useful for finding unDRY code in large codebases that you are new to or aren’t wholly familiar with


IFTTT & the Augmented Human

I’ve been an IFTTT user for a while but only recently have I been trying to use it to its fullest extent. Apps like IFTTT & Zapier are an early version of augmenting ourselves.


The everlasting benefit of naming conventions

Take a look at this example .tern-project file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"libs": [
"browser",
"ecma5",
"ecma6"
],
"loadEagerly": [
"./node_modules/abacus-notepad-component/dist/*.js",
"./node_modules/activity-component/lib/*.js",
"./node_modules/component-popup/src/popup.jsx",
"./server/**/*.js",
"./server/*.js",
"./client/src/js/**/*.js"
],
"plugins": {
"node": {}
}
}

We’re using TernJS and it’s loadEagerly option to have intelligent & dynamic autocomplete available in our JavaScript editor setup. It works anywhere from vim to Visual Studio Code. But I digress.

As out project grows, we add more entries. As our number of projects grow, it will likely get copied all over the place, including onto other developers’ machines that you do not control. Even if you did automate (and even control) the propogation and maintenance of this file, you’d have a problem: the module names, file paths and file names aren’t normalized. A file path is an address. Addresses are normalized in society because they serve a purpose that is not reached if humans cannot make assumptions about them. When identifying structures within the United States of America, addresses are generally normalized to meet the following assumptions:

  • The first piece of the identifier is real number, with the vast majority being integers (a small percentage have a vulgar fraction appended). Most importantly, the overwhelming majority of US streets have the odd numbers on one side of the street and the even numbers on the other. Anecdotal evidence shows this to be even more important than chromatic sequence when locating a structure
  • The second piece is almost always the name of the thoroughfare touching the land nearest the official entrance to the structure
  • The (optional) third piece is a sub-identifier representing that identity of the unit within the structure identified by the preceding and succeeding pieces
  • The fourth piece is the city name
  • The fifth piece is the state name
  • The sixth piece is the Zone Improvement Code or ZIP code. It consist of five- and four-digit integer separated by a hypen (or “dash”). This is, in my opinion, one of the weaker parts of the address system as most folks do not know the 4 digit appendage that was introduced in 1983, nor do they usually know many ZIP codes other than their own

Just as the home address system in the USA does, a file path convention will have stronger and weaker aspects to it. All the same, we all know having an address system is better than none, so why would you not have one for files? In retrospect it becomes an obvious choice.

Take a look at an improved .tern-project file, taking into account the lessons of the address system

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"libs": [
"browser",
"ecma5",
"ecma6"
],
"loadEagerly": [
"./node_modules/abacus-*-component/lib/**/*.js",
"./lib/**/*.js"
],
"plugins": {
"node": {}
}
}

What changes did we make?

  • Name all our team’s custom components using the format teamname-modulename-component
  • Always put our transpiled/consumable JavaScript files in a folder named lib, organized into appropriate subfolders& always using the extension .js. This is the convention, whether it’s a small package or an application

That’s it! We went from 6 entries to 2 just like that.

Happy filemaking!


Automating the hiring process

A representative of an organization called TestDome Ltd contacted me a couple days ago. Today, they pinged me again, asking “Any thoughts on this?”. What they wanted my thoughts on was whether I could use their service of automating using programming quizzes to filter out folks in the (often long-winding) hiring process. My answer was as follows:

My thought is that this type of automation cannot solve the type of hiring problems we have at Netflix. Programming tests embody an extremely poor evaluation of senior programmers. Senior programmers leverage their past experience to effectively combine the best existing solutions in such a way that they’ve created some new, maintainable and sustainable. Senior programmers are good at working with others. They conform to, while incrementally improving, coding style and standards. The only way to pre-suppose about these things is looking through open source contributions, Stack Overflow answers, behavior on Twitter and then pair programming with them. An automated programming quiz merely tests how long they’ve spent doing automated programming quizzes.


The More JavaScript Changes...

..the more it stays the same.

It’s clear I love dynamic languages. I like metaprogramming. I like DRY code. I hate repeating myself (at least when I write code—I repeat myself a lot in person). Objective-C and JavaScript are both dynamic languages and those are the two langs I’ve written produced the most open source code with.

I also like reliable and fast languages. That’s why, although they’re dynamic, I don’t favor Ruby or Python. Python is the better of the two, but I still can’t justify creating the type of web services I write in Python.

That was all setup for the following: Just as Swift is less dynamic than Objective-C, new JavaScript is less dynamic than old JavaScript. Examples:

  • import|export syntax vs. CommonJS
  • More types and implementations of type systems

Static analysis in the language itself isn’t the only reason we’ve gotten less dynamic. Build time tools such as Browserify, static analysis via ESLint, type checking via Flow and several other tools have given us greater safety at the expense of the former wild west freedom.

While I strongly dislike giving up dynamism, I have a much stronger dislike for unDRY (WET?) code. There are a couple recent additions to JavaScript that require a little more thought up front but result in DRYer, safer (and after some re-training of your team) more expressive code. I’m talking about object shorthand syntax and computed property names. Technically computed property names have a duality between safety and danger, but that’s why I love JS.

Object shorthand syntax reduces errors and reader’s overhead by taken advantage of that fact keys and the variables assigned to them should be the same anyway. I’ve always done it that way and viewed variance from that as evidence of not having thoroughly evaluated the why and how of semantics in your application. This syntax is especially valuable in React components, where passing props is common:

1
2
3
4
5
({ dimensions, mappings }) =>
<Component
dimensions
mappings
/>
vs.
1
2
3
4
5
({ dimensionz, mappingz }) =>
<Component
dimensions={dimensionz}
mappings={mappingz}
/>
No joke, I’ve seen plenty of code where the variables were just as arbitrarily named differently from the keys.

Computed property names allow one to (dangerous but powerful skill) dynamically create method names on an object while declaring the object or (safe skill) use constants to name your methods while declaring the object. We’ve always been able to do the following:

1
2
3
4
const mappings = { }
mappings[SOME_CONSTANT] = '<3'
mappings['time_' + Date.now()] = new Date
explode(mappings)
but now we can do either of the following
1
2
3
4
explode({ 
[SOME_CONSTANT] : '<3',
['time_' + Date.now()] : new Date
})

New safety, but also new power depending on which aspect of computed properties you focus on!

The more JavaScript changes, the more it stays the same I guess.


A Proven Cure for JavaScript Fatigue

As an ADHD-addled obsessive who’s been writing JavaScript since 1998, the drive to stay avante garde is nothing new to me. Whether it’s collecting the latest comic books, tech tomes or guitar effects pedals—I’ve always yearned to get my hands on the latest. From childhood on I’ve used a proven set of techniques and principles to guide the way I consume fresh information. The same techniques that helped me transition from a construction worker/dishwasher to a Netflix software architect are the same techniques I use today to upgrade from Babel 5 to Babel 6.

I'm not tired

Now on to JavaScript Fatigue specifically. JSF has relatively recently entered into the web developer’s lexicon. However it’s been a part of technical work for much longer. The difficulty of keeping up-to-date in our industry has been there for many years, but only recently has it become a social pressure. An in-depth exploration of the reasons for the rise of “JavaScript knowledge as fashion” are not in the scope of this article, but the drivers include:

  • Github’s socialization of code
  • Twitter being a key forum in which we examine our place in the industry
  • A rapid increase in salaries—and the subsequent gold rush of smaller-better-faster code jocks. This includes the me-first virus in our industry (see this)

Regardless of the causes, the following 6 principles are the cure for JavaScript fatigue that works for me:

  1. Automate, automate, automate. Any sites that you repeatedly visit for information should be automated as feeds via services like IFTTT.com. An example of doing this can be seen here.
  2. Eliminate all information inputs that are not essential to being the best programmer/manager/artist/human you can be. I follow something resembling an inverted Mad Max version of Pareto Principle here. If a Google Group, newsletter or Twitter user do not produce life-enhancing content to you at least 80% of the time, eliminate it. That Ruby on Rails user group that was really active in 2007 but it a shell of its former self? Unsubscribe. That high school buddy Bradley that was fun in 2003 but now posts 100% negative rants? Bye Bradley.
  3. Constantly replace your previous realms with new ones. If you’ve already covered following 100s of mobile development brothers on Twitter, try following some of the web development sisters. Learn about new areas of life from as many different types of people as possible! Default to saying yes, then revisit and say no aggressively. Say yes to trying new meetups, modules & software, but don’t stay too long if they’re not working for you after you’ve given it the ol’ college try.
  4. Ensure you’re constantly around experts in different but related fields. Contrary to popular wisdom, only associating with teammates that are focused on your realm can result in suboptimal performance as you will spend more time debating choices than making them and executing on them. On my team at Netflix, I’ve an author & former Digital Humanities Specialist from Stanford, a design-savvy D3 expert & a Data Scientist with a business degree around me at all times. We’re all multi-disciplinary and hold one another accountable, but defer to one another on the implementation details of our respective areas of expertise. I learn about new things—so do they—and we focus more energy on learning than arguing.
  5. Allow the wisdom of the crowd to lead you to treasure, but don’t let mob mentality dictate which gems you put in your rucksac to take back to camp. Crowd-sourced wisdom is o’plenty on the web and can be found at Product Hunt, by following key individuals on Twitter & by subscribing to the popularity feeds of Github.
  6. Never study when you’re fatigued unless you’re in-the-zone. Ensure your body and mind are primed to effectively take on new information. There’s no point in going through your info feeds when you’re too knackered to move any of the info into long-term memory. Physical health is deeply tied to mental health too—take walks in the sunshine frequently.

The Future of Editing JavaScript

I think the future lays in having an app like Visual Studio Code seamlessly integrate tools (Browserify, Travis et al) in an agile and open manner. This’d be much like what Slack did in combining Hubot-like hacker features to a user-friendly place. That’d bring the ease-of-use of something like Visual Studio, while also bringin the intelligent & agile hacker-friendly features from the Terminal.