Also from Andrew Faraday

Where else can you find me?

Tuesday, 11 June 2013

Algorithms of the Street - Part 3: Additive Synthesis

I've been thinking about a talk for conferences for a while now, based on the fact that there are certain ideas, or patterns, which keep coming back when we work with computers. It doesn't matter if we're dealing with web apps, processing large files of formatted data, creating spectacular animations or simply playing around with digital audio, programmers meet a lot of the same problems, with variations on the same solutions.

When we learn the patterns which keep coming back, we find solutions more quickly and can move on to bigger and more complex problems. Furthermore, when we learn about these patterns, how they fit together, and understand them enough to implement them differently, we become better programmers.

Just about every capable programmer will be aware of this concept of patterns, and will use them every day. But as I've said here before, understanding problems from first principals helps us to understand how to solve them (which is why I always try to explain things right from the beginning in my blog posts).

So why wouldn't it be useful to start with a new perspective, say, synthesis in Pure Data, and see some common patterns in action?

Proposal over, this tutorial covers another form of synthesis algorithm, Additive Synthesis. I'm going to explain Additive Synthesis from first principals, but also, because it fits quite well and I'm trying out an idea.

But don't worry, I don't assume my blog posts are necessarily being read by experienced programmers, so I'll be explaining the programming concepts from first principals, too.

Step 1: Getting Set Up 

This one's an old favourite, a random sequence of numbers, converting midi notes to frequencies, into an oscillator then out to the sound card. When you click the toggle at the top there, you'll start to hear a kind of melody. It's all very simple and easy to understand. The trouble is, the old problem, it's only producing a single sine-wave tone, which (take my word for this) only contains a single frequency and makes for a very simple, uninteresting and potentially maddening sound.

The reason this type of sound is so grating, is because in nature (or at least, in sounds produced by actual objects), you never usually hear this pure a tone. The sounds you hear contain a lot of frequencies in different proportions, which gives each sound it's own unique tone, or timbre.

Now, musical sounds tend to have one strong, or fundamental frequency, which defines the pitch you hear, and other strong tones over the top of these, called harmonics. These harmonics follow a sequence of relative frequencies known as the harmonic series, which have been observed by physicists in wave forms of all kinds.

In the harmonic series, the first tone is your fundamental, a part ably played by the oscillator we've already created, then the first harmonic is double that frequency, the third is three times the frequency, and so on.

As I may have said before, synthesis is actually the opposite word to analysis. Now that we've analysed some of the properties of musical tones, we can start to synthesize the same idea. We know how to add oscillators in pure data, we know how to do the arithmetic, so we can add a few more harmonics to our existing patch.

When we're reproducing the harmonic series with a synthesizer, we call each produced tone a partial, because it's a part of one single tone.

Step 2: Adding a few partials...

Now we have 5 partials in the harmonic series, multiplying our frequency by the numbers 1 to 5, but it's still quite an unnatural kind of sound. Mostly because these harmonics are at equal volume. As I mentioned earlier the fundamental tone is the strongest, and the higher partials are weaker. Actually, they tend to get weaker by the same amount each time.

So, in the simplest pattern we can (which just so happens to be called a sawtooth wave), lets divide each partial by it's partial number as well...

Step 3: Successively Weakening Partials

So, each one is multiplied by 1 divided by the number of partial it is. The result is what's called a sawtooth wave, the reason for this is because of how it would look plotted on a graph (or, before digital sound, on an oscilloscope)...

Notice how the wave moves quickly up, then more slowly down (bearing in mind that this is actually happening hundreds, or thousands, of times a second), and looks a little like the teeth of a saw. Actually, it also looks a bit wobbly for a saw tooth. In truth, the more partials we use in our synthesis algorithm, the closer this wave shape will be to being 'mathematically perfect'. So lets try upping it to ten partials. And we'll see the first problem with it.

Step 4: Twice as many partials for twice the accuracy

First things first, some housekeeping. I've decided there was too many wires, so I've replaced the wire at the top with a send called fundamental, carrying the fundamental frequency. Then in each partial it's multiplied by the partial number, fed to an oscillator then the resulting tone is multiplied by one divided by the partial number (which are figures I sat and worked out myself), then sent to something new called throw~.

throw~ is just like send~ (or s~), only it can go from multiple sources to one destination. It's what we call a summing bus, as it sums together (adds up) our tones.

The result is that our wave shape has become closer to mathematically perfect (which would look like it's made up of straight lines)...

Only we're not quite there, and we've run out of space on screen, and even with the sends to tidy it up my partials are overlapping each other, wires going over objects, it's only going to get messier if we try to add more partials with this same method.

Imagine a patch like this with 100 partials. Even if we neatly hide them away in sub-patches or even abstractions (which I'll talk about in a later blog post), there's another problem here. Each oscillator takes up a certain amount of processing power, and any computer only has so much (some more than others, but there's always a limit).

We can get a wave produced with more partials, but we need to start thinking differently about what we've just created.

This patch uses processing power for 10 oscillators (plus 9 control multiplies and 9 audio multiplies) constantly, to produce a sound which is always going to be the same, so why should your computer constantly be working out this waveshape in order to synthesize the tone?

What we need is known in programming terms as an early-binding system, one which does the hard work once then uses a more efficient method of playing back this tone.

What we need in Pure Data is wavetable synthesis. In which a waveform is stored in an array then read from that array at audio speed to produce the tone. The osc~ object actually reads a sine-wave table internally to do it's thing, we'd just be giving it a different lookup, to the result of 100 paritals can be heard while only using the processing power required by one.

Pure Data even has a method called 'sinesum' which will sum up some sinewaves for us, we just need to tell it the levels we want our partials at (so it's still additive synthesis we're doing, just early-binding additive synthesis, instead of the inefficient late-binding additive synthesis we were doing).

Step 5: Lets make a wavetable synth...

What's happening here, then? Well, you'll notice that I've discarded most of the patch, and brought us back to what is in effect the same as the patch we started with. Only instead of osc~ I've got tabosc4~ and the name of an array, waveform. The array, waveform, contains a sine wave, and below it there's a message which is sent to the array via s waveform.

This message is a special message to an array to do additive synthesis with a list of numbers. The first is the length of the array (sinesum will re-size it for us) and to work with tabosc4~ it has to be a power of 2. 32768 is 2 to the power of 15. Then the rest of the list is how loud you want each partial to be. So one partial with an amplitude of 1 is just a sine wave. When this message is clicked Pure Data processes the list of numbers and puts the result in the array for us.

You've probably realised by now that this also sounds exactly like the patch we started with, only to get to the point we were just at, we don't need all those oscillators, we just need to put the list of amplitudes in our message box. (remember, each one is 1 divided by how far along the list it is).

Step 6: Additive synthesis with just a list

I've just expanded my message to have the list of amplitudes I worked out earlier, following those same rules, and added a comma to it (which is the same as sending a different message immediately after) with the word normalize (meaning 'make it fit this amount') and 1. This means that the result will be made to fit between 1 and -1. We can see our sawtooth wave again, and hear it. Only now we're not using all that space on screen or all that processing power. Win!

That's more or less it for Pure Data, but there is one more problem here, and it's not one that's easily solved in Pd. I have however, found a solution which involves using a different tool, the programming language, Ruby. This leads me to another pattern in technology we need to be aware of:

When you have a hammer, everything looks like a nail.

In other words, the tool you're using isn't always the best one to solve the problem.

The problem is that after finding the rule for the partials of a saw wave, I had to sit here and work them out, then write them down. And if I wanted to use a different rule, I'd have to work them out again. Okay, I can use a calculator, but that's still a little tedious. So what if I could write a program to write the list of partials for me. Then we could get 100 partials, without having to work out 100 numbers.

Warning: The following section contains some ruby code, and I might not explain it in depth. Feel free to talk to me in another medium if you want a bit more info.

Step 7: Different kind of nail? Different kind of hammer!

This looks much simpler, but you'll notice that the list of partials isn't there any more. Instead the send to the array, waveform, is fed by an object called netreceive. This simply reads everything that's sent to your computer on that numbered network port. I'm not going to get technical here, but this port is just a destination for your computer to get messages from other computers such as the contents of websites, shared files etc.

Here, however, we're going to pass it information from the same computer, with a Ruby script. Copy the following into a text file (in a plain text editor, not a word processor) ending the file name with .rb:

require 'socket'
connection = 'localhost', '4000'

partials = []
100.times do |i|
  i = i+1
  partials << 1.0/i

connection.puts "sinesum 32768 #{partials.join(' ')} ;"
connection.puts 'normalize 1;'

So, you might need to know a little ruby to understand this, but it's basically loading the standard Ruby network library, socket. Then using it to make a connection to port 4000 (same as netreceive) on 'localhost' (which is always the computer running this script). Then for the numbers 0 to 99, it adds one (so we're effectively using 1 to 100) then divides 1 by that number and adds it to an array, partials (just like in Pd, this is a list of numbers, although it can be other things in Ruby). Then it sends the message sinesum, that same length, and all the partials it's worked out, separated by spaces.

Incidentally this is the same method of communicating between Ruby and Pure Data is the same as I used in Text to Music.

You'll need ruby installed (usually it is installed by default for Linux and Mac boxes, just don't ask me for help running it on windows). Then you can run this script (in a terminal, 'ruby' then your file name) and it will generate the right amplitudes for 100 partials and pass it in to Pure Data, where it is picked up by netreceive and sent to the array, waveform.

As promised, we can see (and hear) a near-perfect saw wave created by 100 partials...

But Andrew, I hear you say, surely we can use this method to produce other waveforms with this method?

Yes you can, and I have two places for you to look to be able to do this.

  • The Pure Data FLOSS guide (where I initially got the equation I'm using here, and two other commonly used ones.)
  • A github repository I put together to demonstrate this technique. It's also a working example with a few common waveforms.
I've not actually seen any material online about automatically producing partial numbers for additive synthesis in Pure Data. It seems I'm the first one to write about it publicly (feel free to correct me on this). So hopefully it will make a few lives a little easier, a common hope of programmers the world over.

That's it, enjoy playing with Additive Synthesis, and hopefully you've learned a little about those programming patterns. As ever, feel free to leave a comment or contact me on twitter (@MarmiteJunction).

No comments:

Post a Comment