Art of Softsynth Development: Philosophy and Tradeoffs

Jul 24, 2013

Introduction

In this article series, I’ll share my most valuable lessons from 5 years of synth development. I expect the series to run for a while, and I’ll post articles in no particular order. I will start by covering the core of my approach in this article, so you can better see where I’m coming from, and if this series is something you’ll be interested in.

A combinatorial explosion of choice

Software synthesizers can be very simple things, but they can also be very complicated things. It’s easy to grab a few oscillators and filters off musicdsp, put them into a VSTi, and have something serviceable. But once you dig a little deeper, software synthesizers can be very elusive beasts.

The problem is choice.

Evaluating solutions

In programming, you can usually judge your solution to a problem. Is it good or is it bad? Is it better or worse than this other solution? Sometimes you’ll be able to objectively judge the solution, because the universe agrees on criteria for good solutions. Sometimes, judgments will be more subjective, and more a matter of personal taste, and you’ll still be able to compare solutions with some confidence.

Occasionally, you might program something where you can say definitively whether a program functions correctly or not. For example, the core functionality of a calculator must be either right or wrong, and we can absolutely objectively say which is which. Software synthesizers are the complete opposite. No objective criteria exist for determining whether a sound is good or bad - except for your ears! How you arrive at a good sound - well, from the perspective of “good sound”, the process is inconsequential.

But whether the sound is good or bad is in fact the least of your worries.

Choices

Here are some of the high level choices you have to consider at the outset:

But even these choices are not your biggest worry. The meaty choices arrive at the next level down: How will you implement the synth? What algorithms will you use? How will you store your data? How will you ensure cache coherency? How will you do your SIMD parallelization? What is your multithreading parallelization strategy? How will you handle denormals? What is your modulation update strategy?

Once you have considered these choices, and many many more, then you can start worrying about whether the thing sounds good or not. And at this point - the only thing you have to go by is your ears. If it sounds good - then it is good.

It’s an optimization problem!

One way to look at softsynth development is as an optimization problem. You have a multitude of choices in many dimensions, and you’re trying to find the set of choices that result in the “best” solution.

What I hope to present in this series of articles is choices which - sometimes by themselves, sometimes in interplay with other choices - make your softsynth better.

Tradeoffs

I already outlined a few of the tradeoffs you’re facing above.

Here’s a summary of the most important ones (in no particular order)

In a demoscene context, size is usually a hard limit. Either hard hard for executable music competitions, or almost hard for intro competitions where the synth is used as part of the intro. This means that binary size must always be in our minds.

By versatility I mean how usable the synth is as a general instrument. How many different types of convincing sounds is it able to do well?

We are mostly worried about implementation complexity: Inherent complexity introduced by the choices made above, and of the methods, data structures and algorithms we choose to implement.

You have to keep these tradeoffs in mind as you make your choices about your softsynth.

Philosophy

Given the above, what should our philosophy be when writing software synthesizers?

The main thing I learned after 5 years of synth development is that there is a lot of trial and error involved in the process. Getting to the “good” sound sometimes requires bumbling about for a while. With that in mind, I postulate that your absolutely most important guiding principle should be:

Simplicity!

It is easy to overengineer a synth, creating an unmaintainable and unextendable mess. Simplicity has to be your first concern. Make choices that simplify the structure, choose simple data structures and choose simple algorithms! That way your bumbling about becomes enjoyable, and the chances of you making a good synth become much greater.

Metcalfs law states that the value of networks grow proportionally to the square of the number of connected nodes. I believe a sort of inverse of that applies generally to software development, but specifically to softsynth development (because a synth is one big interconnected mess):

Keeping individual things simple means that system complexity only grows logarithmically rather than exponentially.

Approximately.

And this is essential to keeping the synth is a state where you can keep working on it, and be happy doing so.

Simple = good

Simple solutions also tend to be small, reducing binary size. Since binary size is probably the second thing you should be concerned about, that is good.

Another advantage of simple ideas is that they are usually simple to implement, simple to reason about - resulting in fewer bugs in your synth. I have personally witnessed the power of this in the lastest synth I’ve been working on, where I really went all in on the concept of simplicity. The bugs here have been easy to solve, and there haven’t been many of them. It was a real joy to see the synth be stable right from the get go. Now, contrast this with my commercial synth Quiver, where the tradeoffs were different - in favor of performance and sound quality. It took me months of work to rid it of subtle crash bugs.

Simple = stupid

Now, you might object that simple solutions are generally dumb solutions - with “dumb” meaning it either performs badly or doesn’t sound good. Fortunately, we have two outs:

  1. It is only generally true that simple solutions are slow or sound bad. If you keep your eyes open, once in a while you’ll stumble on a technique that is both brilliantly simple, fast, and sounds good. Once you find one of these, you hold on for dear life and milk it for all it is worth.
  2. Not everything in a synth has to be highly performant and sound absolutely perfect. Fortunately, a non-trivial part of your synth will be infrastructure, and this part should absolutely be as simple as possible. For other parts of your synth, well, you’ll have to take a hard look at your tradeoffs, and factor in that simplicity will mean even more than you realize.

So keep it simple

Synthesizers are hard. You face thousands of choices. Make choices that simplify your solution. This makes development by trial and error practical, which you need to make a good synth.