As of this writing, Goodhertz has two employees. Neither of us are great at mental math (splitting a lunch bill is sometimes difficult), but one of us is good at high level math. By which I mean the big league calculations: mathematical modeling, calculus, algorithms. When everything is going well, this kind of math will heat up your computer just a little. (At other times, in our lab, it heats up our computers a lot.) One of us writes the math that turns ordinary sounds into Goodhertz sounds.

I’m the other guy. But don’t worry! The code I write is math-free. Mostly. I write code that transforms data into interfaces and boilerplate. That is, I write code that makes it easier to build more plugins. Too often in audio software, the audio programming gets buried in the mundane details of making software work — the kind of programming closer to filling out forms than scribbling inspired equations on a blackboard. At Goodhertz, we want to eliminate the former and encourage the latter.

We want cut the shortest path from sonic novelty in our lab to sonic novelty in your music.

Building a Plugin

O.K., let’s build a plugin. Let’s call it... Beovulf.1 Let’s say it’s got... one slider. And one mode switch. Very simple. (To be clear, I don’t really know how other plugin companies build their plugins. But I have a feeling it’s not a lot like this.)

Fire up pb, our in-house plugin-building tool, on localhost:5001.

Enter a new catalog number, i.e. 0019, and hit enter.

Now you’ve got a whole new directory, plugins/GHZ0019, in which sit, among a number of other files, three very important ones: GHZ0019DSP.h, GHZ0019DSP.mm, and config.yml.

The first two — .h & .mm — are mostly empty now, though they will soon be full of Goodhertz algorithms. Which is Devin’s purview. To me, the files are a black box. Thousands of floating-point digits stream into the black box every few milliseconds; thousands more, modified, stream back out. Numbers in, numbers out. Simple — although only if you think about those two files as a black box.2

But how do the thousands of numbers get to the black box, where do those numbers come from, and how do the black boxes know what you want them to do? That’s the next step, (4): Fill out config.yml.3

variables:
  THEME_COLOR: "SlateGray"
metadata:
  beta: true
  catalog: "0019"
  pluginName: "Beovulf"
  color: THEME_COLOR
  interfaceLayout: |
    0
    1
  height: 200
clumps:
  - metadata:
      name: "Saturation"
      label: "SAT"
    parameters:
      - name: "Saturation Amount"
        label: "Amount"
        initialValue: 50
        substituteStringsForValues: { "0.0": "Off" }
        color: [saturate, THEME_COLOR, 10]
        horizontal: true
  - metadata:
      name: "English"
      label: "ENG"
    parameters:
      - name: "English Mode"
        unit: "Indexed"
        stringValues: ["Old", "Middle", "Modern"]
        color: [desaturate, THEME_COLOR, 10]

From there, it’s just (step 5) one click to this:

As far as pb is concerned, that little bit of configuration is precisely equivalent to that fully-operational interface.

Of course, that interface can’t be gleaned from just those bits of data. Take English Mode for example, the second parameter. Given what’s specified in that config, pb infers the rest, which ends us being a lot:

(param-keyed (get-config :catalog 19) "EnglishMode")
 
=> {:automatesSmoothly true, :maxValue 2.0, :clumpName "English", :horizontal true, :aaxRealPrecision 1, :showsScalar false,
:enumIdentifier "GHZ0019Param_EnglishMode", :frame [0 0 338 85], :color "#7D8083", :controlFocusShape "Default",
:parameterType "Discrete", :unit "Indexed", :controlStyle "Pilled", :name "English Mode", :hasStringValues true,
:clump 2, :codesafeName "EnglishMode", :steps 3, :labelString "English Mode", :layoutKey "0", :presetWildcard false,
:controlScale 1.0, :scope "self", :formatString "%1.0f", :stringValues ("Old" "Middle" "Modern"), :labelDisplay "below",
:internalValueMapping false, :hostLabel "English Mode",:auLogarithmic false, :pid 2, :granularity 1.0, :controlType "Itemized",
:coarseGranularity 1.0, :hidden false, :initialValue 0.0, :minimalName "em", :hasUnitSymbol false, :interactionCurve "Linear",
:cueSymbolDisplay :below, :border [0 0 0 0], :hostClump "English", :fineGranularity 1.0, :logarithmic false, :indexInClump 0,
:minValue 0.0, :includeCrementers true}

Lots of that will be inscrutable to people who aren’t Goodhertz employees, but some of it may make some intuitive sense. For instance, Pro Tools wants to know if a parameter is "Continuous" or "Discrete". Since English Mode is "Indexed", we infer that it’s "Discrete". We also infer that it should be displayed as an "Itemized" control, which is Goodhertz-speak for a control that can be cycled-through (with little arrows), but can also display a dropdown menu for quick-picking. That said, a minimal edit — controlType: "Toggle" — gets us something a little different, and potentially better-suited to this use-case:

This leads to an interesting conclusion: Goodhertz interfaces are data (not code). Which means a lot of things, but here’re a few big ones.

More plugins

(1) Plugins Built on Data are Easier to Build

All this data doesn’t just lead to interfaces, it also solves the most cumbersome part of any plugin development process: boilerplate. Most often, plugins are built in such a way that they contain a lot of code, and a lot of assets (e.g. 65 different images of a single knob), which results in a lot of MB’s that you, as a customer, have to download.4

Goodhertz plugins attempt to be as small as possible, in lots of ways. We keep the code and CPU footprint small by doing as much numeric precalculation and unrolling as we possibly can — grist for the C compiler. We also keep the interfaces “small” in the sense that our design language consists of only a few primitive components, which makes our interfaces easier to understand, both for us (the designers) and for you (the music-makers). Easy to build, simple to use. Powerful at both ends of the pipeline.

All in all, we built this system because we wanted to make building plugins easier, and because we believe that the most difficult work we do here at Goodhertz — the work that requires the most intellectual effort and consumes the most time — should be (1) writing DSP, (2) explaining that DSP, and (3) improving our user interfaces. Everything else is just bureaucracy; pb is our red-tape-cutting machine.

(You may have noticed that, as a two person company, we’ve released 11 plugins before our first anniversary.)

 

More formats

(2) Plugins Built on Data are Easier to Port

When you start with data, programming languages and platforms matter a lot less. We already sell plugins in the Mac AU and AAX formats, but iOS and Mac framework versions of our plugins are only one click away.5

But what about more plugin formats? For the last couple of months, we’ve been asking that question: what plugin format do you wish we supported? Hundreds of you have been answering, and in the near future we’ll be asking for your help to make those formats happen. Windows people, that means you. And iOS people.

 

More features

(3) Plugins Built on Data are Easier to Improve

Goodhertz plugins all share a single core interface. They might look different, but the interface code in Trem Control and Tone Control is exactly the same. As a result, when we fix a bug in one, we fix a bug in all of them, and we make those fixes available all at once. All of our plugins are constantly improving; none are gathering dust on the shelf.

 

More questions

(4) Plugins Built on Data are a Database of Plugins

Because we start from data and expand to more data, we can treat our plugins like a database.

Let’s start up a Clojure REPL6 try out that last assertion. First things first, we need to gather up all the parameters in all the plugins.

(def all-params (apply concat (map :parameters (get-release-configs []))))
=> #'pb.core/all-params

Now we can ask questions, like: how many parameters are there in all the public Goodhertz plugins?

(count all-params)
=> 191

Or, how many of those parameters are sliders that control frequency?

(defn freq-slider? [p]
   (and (= "Hertz" (:unit p))
        (= "Slider" (:controlType p)))
 
(count (filter freq-slider? all-params))
=> 19

That last one may seem contrived, but knowing concrete facts about our interfaces enables us to think about them on the whole, and leads us to more informed decisions.

For instance, should we spend more time perfecting the mechanics of frequency sliders when we only have 19 of them (out of a total of 191 controls)? Doesn’t quite seem worth it. But what if we sort the plugins by number of frequency controls?

(->> (get-release-configs [])
      (map (fn [{:keys [metadata parameters]}]
             [(:pluginName metadata)
              (:catalog metadata)
              (count (filter freq-slider? parameters))]))
      (sort-by #(nth % 2))
      (reverse))
 
=> (["Midside" :0013 6] ["Tiltshift" :0015 6] ["Lossy" :0005 2] ["Tone Control" :0003 2] ["Faraday Limiter" :0007 1] ["Lohi" :0006 1] ["Trem Control" :0004 1] ["Good Dither" :0012 0] ["Panpot" :0009 0] ["Vulf Compressor" :0002 0] ["CanOpener Studio" :0001 0])

Interesting that our newest plugins — Midside and Tiltshift — both use so many frequency sliders. Given that frequency sliders are on the rise, perfecting them seems like time well spent.

You may be thinking: some of these points stray pretty far afield from the world of audio. But, as Devin discussed in his recent blog post on automatability, it’s hard to know if a plugin really sounds good if its interface is getting in your way. By that same token, it’s hard to know if a plugin idea sounds good if — owing to the difficulty of building plugins — that idea never sees the light of day.

In fact, that process doubles back on itself: the ease of building encourages experimentation, and the freedom to experiment leads, in turn, to ideas unimaginable without the flexibility of pb and our in-house DSP libraries.

Lossy was, at first, a straightforward realtime lossy compression simulator — just two sliders. Every other control you see was added after the plugin was “done.” Which makes sense to us. The distance between a Goodhertz prototype and a Goodhertz plugin has collapsed to zero. When we experiment and evaluate our ideas, we’re evaluating the real thing. Or as Devin put it recently in Slack:

I feel like when we started making plugins I used Max/MSP a lot. Now that pb has gotten so good I never open it. It’s just easier to make it than to prototype it.

Want to know more about how we build plugins? We’re an email away: goodhertz.co/contact

Footnotes
1Nothing vexes us more than naming our plugins. That’s why we recently started referring to in-development plugins almost exclusively by their catalog numbers, which are assigned as soon as an idea is good enough to commit to code. This partially explains why Midside, our 11th plugin, has catalog number 0013, but Tiltshift, our 10th plugin, has catalog number 0015. Devin started work on Midside and inadvertently created a revolutionary tilt EQ, so we thought: this should probably be its own plugin.
2Stay tuned for future blog posts in which those are not thought of as black boxes; in which Devin takes an inverse view of the Goodhertz plugin-building process.
3One side-effect of my time working at Twitter: I used to think YAML was awesome. I still kind of do, but the complexity of our config specs has quickly outpaced the benefit of such a simple configuration language. We’ll be transitioning our config-editing process to a rich database-style interface in the future.
465 isn’t a made up number, it’s the number of images for each knob in one popular plugin, released in 2012. The grand total for that plugin comes to 482 images.
5If you build a product and you’re looking to license Goodhertz DSP for it, let us know. We’d love to talk specifics.
6Why a Clojure REPL? Because we use Clojure to build our plugins. Why do we use Clojure? Great question, though one I’ll answer more thoroughly in a future blog post. For now, I’ll say simply: when you think about a build process as a single (complex) transformation of data to code, then Clojure makes a ton of sense.