Wire Up a Synth with the Web Audio API

In my last post, I wrote about rendering graphics programmatically on the web. This week, I’d like to do the same for audio. Instead of dropping in GIFs or MP3s, the creative tinkerer can generate sound and image using some elemental yet powerful syntaxes, right here in the browser you’re reading in. (Scroll down for a live demo!)

The synth geeks on Splice know what I’m talking about. Many of the keyboards, basslines, and leads we know and love are produced in the same way they were a half century ago on analog electronics: The performer sets controls and plays into a graph of nodes which generate, process, shape, or recombine audio signals according to specific mathematical functions. The most sophisticated software synths earn notoriety, like Massive, our current #1 synth on Splice, and the arguable backbone of US dubstep.

The totally bananas part is that, via the Web Audio API, web browsers are beginning to support this functionality normally reserved for native plugins. It’s still experimental (Sorry, Internet Explorer and Android), and a bit rough around the edges, but exciting nonetheless.

Of the libraries which have sprouted up around this functionality (including Flocking and Audiolet), I’ve chosen to highlight timbre.js in this post, which does an excellent job of abstracting away the wiring up of audio nodes in an elegant DSL.

To explain, here’s a rough graph of my simple, kinda organic-sounding synth patch:

timbre-graph
Because there are no crosswise interdependencies between nodes, timbre.js lets me describe all this in a set of nested JavaScript function calls:

var synth = T(
  "perc", {a: 100, r: 400},
  T("sin", {freq: freqSlide, mul: 0.25}),
  T("sin", {freq: freqSlide * 1.01, mul: 0.05, phase: Math.PI * 0.25}),
  T("eq", {lpf: [100, 0.0, -48.0]},
    T("sin", {freq: freqSlide * 2, mul: 0.25})
  ),
  T("perc", {r: 10},
    T("saw", {freq: freqSlide * 4, mul: 0.05})
  ),
  T("perc", {a: 100, r: 100},
    T("noise", {mul:0.0})
  )
)

The first T() in the code snippet corresponds to the rightmost node in the schematic. The genius of timbre.js is to allow splats (unspecified numbers of arguments) of other T()s to all be summed together as audio signals. So when you use the elemental T() function, the first arguments describe that node—and all subsequent arguments are summed and routed right through it. This allows for the expression of complex synths in very short definitions.

You can hear my amateur sound design here, tuned to a just-intoned major scale:

While it may sometimes be marred by clipping (i.e. harsh artifacts), synthesis in the browser is an exciting development for online games, synths, and who knows what else. Have fun playing, and please reach out to me if you’d like to know more!

August 22, 2014