This is a follow up tutorial to the "Beginners quick start guide to making music in Puredata". This will be useful for people who want to make music using Pd and synthesised sounds. In this exercise we are going to build six synthesisers and connect them up to play a piece of music. Again we will be using nothing but Puredata so make you have it installed. Each synthesiser will use a different synthesis method. We will continue to focus on electronic "techno" music and not be fussy about our synthesis model or the accuracy and realism of the sounds we make, instead we are going to build quite open ended synths with a range of timbres to explore. For each synth we will quickly discuss its basic principle and harmonic structure and then proceed to think about its interface and how we would like to use it. Finally we will build a control structure to test the synth and make sure it's doing what we want.
Of all the synths known to humanity the most recognised and peculiarly seminal sound is the filtered saw wave. Literally hundreds of synths are built on this most basic architecture. The sound is so familiar as a voice in modern music it is quite synonymous with the word "synthesiser" when describing a sound quality. The instrument we are going to build first is like that of an SH101, Juno or Moog type voice. This basic pattern is sometimes mistakenly called an "acid" sound. The most salient feature of the 303, the source of real acid sounds is the unique glissando slide, which is difficult or impossible to produce in midi composition environments, and the particular high resonance of the filter. A good 303 emulation is a quite advanced exercise we will leave to another time. Our synth is simple so that we can look at the basics of building music sound synthesisers in Pd.
We begin by summing just two sawtooth waves. We use a [+~] object to do that explicitly for clarity. The output has two problems. Firstly it's a bit loud, so we multiply by 0.1 to get a quieter signal. The second problem is more subtle and not something you can immediately hear, but the signal has a DC offset. Phasors start at zero and go to one, so because they are not symmetrical about zero their average is always positive. By subtracting 0.5 from each phasor we center it around zero which makes our sound less prone to unwanted distortions. As you can hear the result of mixing two saws is a nice bright buzzing stringy sound. Puredata file .pd

That sounds good but it's too bright all the time like that, we want to be able to control the brightness so lets add a filter to remove some harmonics. We use a bandpass filter, which is the [bp~] object. The first inlet of a [bp~] object is the audio signal to be filtered. The second inlet or parameter of a [bp~] is its frequency, for a bandpass this is the center of a band that will pass through, every frequency above and below will be reduced. Create one and type 660 for its first parameter and 3 for its second. 660Hz is a nice low frequency and its also the second harmonic of the saw waves we placed at 220Hz which is a good place to put it for string sounds. Puredata file .pd

We would like to be able to control the cutoff frequency of the filter. While we are at it lets make a volume control to change the amplitude of the final signal too. Create two sliders and set the range of the first one between about 100 and 2000, we will use that for controlling the filter frequency so give it a name to display like "filter frequency" or something. Connect the bottom of it to the filter frequency inlet. The second control should range between zero and about 0.3 and we will connect that to the righthand inlet of the final multiply, so it is no longer fixed at 0.2 but can be varied. Puredata file .pd

We dont want to have to use the slider every time we want the filter frequency to vary, we can attach that parameter to an automatically moving signal. One of the simplest low frequency message objects is a [line] generator. When you send a message containing a pair of numbers to one it slowly changes its output in a linear fashion. The message consists of a destination value and a time in milliseconds. Whatever the current output value it will then move to the destination value taking exactly the specified time. Making an envelope generator starts with placing a [line] and creating messages for the "attack" and "decay" parts. Here we have used two messages to make the filter rise over about 2 seconds, and decay over exactly 7 seconds. The destination values are 1 and 0, which is very useful. By having the line generator output move between 1 and 0 we can multiply it by any scale we like to use it for different things. In this case we have made the range between 100 and 2100 by inserting the [* 2000] and [+ 100] objects. The frequency control fader has been temporarily disconnected because we are using the line generator instead right now. Puredata file .pd

To automate the line generator and make it a proper envelope we need to send two message in sequence. These will set the line generator in motion by itself to rise up to the top in a time set by an attack slider, and then fall back to zero in the time set by a decay fader. We can construct larger messages by substituting one value in another which we do with the [$1( notation. We also need to store two temporary numbers from our attack and decay sliders which go in [float] (or just [f]) boxes. When the left hand inlet of a float is banged the box outputs whatever value was stored on its rightmost inlet at the time. We will use a bang button to send the signal to start the envelope generator. Let's make our first message fire off the contents of the float box from the attack slider. The line will now begin to rise towards 1. The trick to hold off firing the decay part is to use a delay. The [del] object delays any bangs appearing at its left inlet by the time appearing on its right inlet, which we take from the attack slider. Clicking the bang button now automatically runs both parts of the envelope segment. I have also adjusted the lowest range of the filter a little to make it sound better, and added a [hip~] highpass filter gently remove any very bassy frequencies. Puredata file .pd

Now we have a working envelope generator, but it's a bit messy and hard to follow. All we need to know is the attack and decay times and have a way of triggering it and getting an output signal. To tidy up the patch we next make a subpatch of the envelope generator. To do this create a new object and type "pd envelope" into it. This creates a new empty subpatch. Anything beginning with pd and a space is taken to be a subpatch and you can put whatever you like inside it with your own inlets and outlets. The word "envelope" is just a name to identify it and can be anything. Next I selected the components of the envelope generator and cut them with CTRL-X and pasted them into the new subpatch with CTRL-V. Look inside the subpatch called [pd envelope] in the next example. So that the envelope generator always starts with some sensible values the float boxes for the attack and decay have been set to 50 ms. Values from the attack inlet are sent to the delay and float box nicely using a [t f f ] unit. This makes sure we know the order that signals are sent and makes debugging patches much easier. Now we could reuse this subpatch by copying it much more easily, but the main reason now is just to organise our main patch. In the main patch we have also reconnected the filter frequency slider to be the base frequency of the filter. Puredata file .pd

There are two small issues we face now which will be corrected or changed shortly. Firstly, the controls are only updated when a new note is triggered, when the bang is pressed. Secondly the synth uses the default parameters set in the float boxes and messages until the controls are used to override them. Later we will look at creating preset parameters or "patch programs" to memorise good settings and have the synth load them up when it is created. To further tidy the patch let's work on the oscillator. We've created a subpatch of the oscillator in the next example file and given it a pitch inlet and a "fatness" control. The pitch inlet of a standard [osc~] unit is in Hertz, so concert A is typically 440Hz. Fatness is the separation of the sawtooth waves which creates slowly moving "beating" patterns. We have started by assuming that fatness will range from 0 to 1 and multiplying that by a small number which is added to the frequency of one oscillator and subtracted from the frequency of the other. A spacing of between zero and ten or so Hz is plenty of fatness, so we subtract up to five from one oscillator and add as much as five to the other. See how we used a trigger object to separate out some bangs so that the oscillator is updated whenever the fatness changes. This works by first sending the float part to the righthand inlets and then sending bangs to the leftmost inlets of the addition and subtraction objects.

Recall that the triggers evaluate their signals from right to left, so the bangs are the last messages sent. Let's also do this for the filter frequency and range parameters in the main patch so that those controls are continuously updated. Finally we have given our synth a resonance control for the filter, ranging between 1 and 6. Notice that we used a different kind of outlet for the oscillator subpatch, it is a [outlet~] type object for audio signals. Puredata file .pd

Our synth can make some pretty nice pad sounds now, but it is not yet complete. We would like to be able to send it midi note values and control the amplitude too. The second part is fairly easy. To control amplitude all we need to do is multiply the output signal by another envelope, and we can copy our envelope subpatch to do that. The envelope generates a value between 0 and 1, so we can just hook it right up to a multiply without having to scale it. I've added two more controls for this too, one for amplitude attack and one for amplitude decay. Now we have a fairly messy looking patch again, we need to move our sliders and DSP code apart. Before looking at midi control in the final section we will next move onto section two and build an interface which defines the way the synth looks and responds to control input data. Puredata file .pd

What we've done in this next patch is just tidy it up a lot. We have been adding sliders as we went along, whenever we fancied making something controllable. Now we need to put those sliders on an interface panel so first, everywhere we find a connection from a slider we connect it to a named [send] connection. For each of those we add a corresponding [receive] unit and connect it to the sliders original destination. This prepares us to separate the interface. Puredata file .pd

Now we have the DSP code on the left hand side, and the interface on the right. Some of the parameters will still need adjusting, like the pitch control which will shortly be redundant, but an interface layout is beginning to take shape. Next let's create subpatches for each part, synth engine and interface. Puredata file .pd

Now we can add some colourful decorations. There are lots of nice GUI elements available for Puredata, and you can build external interfaces using Tk, Grip or GTk as well as other options like making your synth a VST or LADSPA plugin, not all of which work perfectly just yet, but it's possible to make synths very pretty and flexible with the right GUI tools. Here we've stuck to simple basic elements that will be available in any Pd distribution, things like number boxes, sliders and canvases. With the DSP part of the synth subpatched it begins to look nicer. Puredata file .pd

With the two parts separate it's easier for us to see what is going on and treat each thing in it's own window. Open up the "synth1" subpatch in the next example file and see how the octave selector and midi note decoder replaced the pitch slider. Midi notes are numbered 0-127, and our oscillator takes a number in Hertz. We need to convert from midi numbers to frequency, which is what the [mtof] object does. If we want to move up an octave by changing frequency we double it, but if we want to change octave using a midi number we must add 12 to it. That is why the octave shifter goes before the [mtof] unit. Can you see how we cheated with the radio button selector which only gives zero or positive values? Subtracting two from it lets us choose negative octave shifts down as well as up, so the default position for the radio button selector should be in the middle now. With some midi notes in messages hooked up to it we can play the synth by clicking message boxes. If you play with the controls on this synth you will see that some numbers are too big to be shown in the 2 digit number boxes we made (the box shows + or - when a number is out of range). Some of the controls range from zero to one, some from zero to a thousand or more. We would like all the controls to move over the same range, in other words we normalise the controls so that they all move from zero to one. When we do this it makes interfacing to external signals like midi controllers easier if all sliders share a common scale. Puredata file .pd
For fun there's a random midi not sequencer hooked up to the synth next. But the real work to do in this section is to normalise the controls. It's best to always try and keep your controls between one and zero as floating point numbers. It makes life easy for scale calculations and in this next part we have had to change every control sliders range. To compensate for that it's necessary to inset multipliers and offsets here and there in the synth DSP to rescale values. Multipliers set the slope and offsets to set the lowest value. Sometimes to force parameters to stay within safe values a [clip], [min], or [max] units may be used, but we haven't deployed any here this time. Since the changes are invisible there's not much to see here, have a look at the Pd patch instead and examine the fader ranges. Puredata file .pd
One thing we want to have is a way to store patch memories, the settings on each slider as a snapshot. To do this we give each slider a "receive symbol" as well as a "send symbol". Each now slider broadcasts its value as a number to the named destination. With these names as message destinations we can pick up or send all the values for the sliders to or from a list. Open the subpatch named "control interface" in the next patch example. Here we have used the [route] object to split a list of name and number pairs and send them to the correct slider. The patch programs themselves are stored somewhere else, in this case just floating in the main patch. They get sent to a named destination for our synth, in this case "Pluto" (It's traditional for analog synths to be named after planets whether they technically qualify as planets or not). The patchname is displayed in the symbol box and all the faders are moved as soon as you click a message. If you notice the ranges for controls are all converted from a range of 0-127. This is to take us into the final part where we add a proper midi interface to the synth. Puredata file .pd

Last thing to do before wrapping up this synth is to give it a few preset programs and give it a proper midi interface so it can be controlled by an external sequencer. It doesn't need to recognise "note-off" events, because the envelope is a simple fire once AD type with no gate or hold time. Let's insert a [notein] unit to decode incomming midi signals. This picks up all midi on all channels sent to Pd , but we can filter out unwanted data. We will set the default midi channel of our synth to 1 and give it one controller to adjust the filter frequency using a [ctlin] object. Of course you could automate any controls, add a pitch bend or midi volume controller, we have just added one more feature, getting the synth to respond to a program change message. But there are only 3 patches to choose from :) Puredata file .pd
That finishes our first synth. It has many failings and is quite crude, but hopefully you will understand because you built it. One problem you may not easily diagnose is the limitations of the [bp~] unit that causes clicking far more than some other less simple filters we could have used. I've made some final adjustments to this last version, see if you can find them. Here is the tidied up version with an audio [outlet~] for use in compositions. Puredata file .pd
In this section we are going to create a new synthesiser with an unusual 1990s type sound. It's good for percussive sequencer lines fitting high energy dance, or sounds like A-ha, It is reminiscent of the Casio PD synthesisers that did phase distortion, or some of the harder FM capabilities of the PPG. Waveshaping is where you introduce a non-linearity to a signal. The effect is to create new harmonics that depend on the amplitude and frequencies of components in the original signal. Choosing the right function for a waveshape allows us to predict the way harmonics evolve as the signal changes in amplitude. In this exercise we will use [cos~] and [clip~] as waveshapers.
Moving faders f1 - f3 changes the pitch of each individual sine wave oscillator. Again we scale them back to about one third of their amplitude before mixing them. Couldn't we sum them and then divide to save operators? Yes, the dynamic range of Pd while the signal is digital is just a floating point number of the machines accuracy, usually 32 bit these days or 64 bit for the lucky few. The time when you must obey the 1 = maximum rule is at the [dac~] output, because there is where 1 is "full dynamic range" to the rest of the audio system, but before that you can defer rescaling in many cases. This is a good example of why you might want to "eagerly" rescale signals, because it helps us use [clip~] more easily. We want a signal clip of about 50% symmetrically about zero and having the sum of the oscillators be already normalised makes keeping track of the clip parameters easy. Actually the maximum signal peak is +/-0.9, so we get a little extra headroom.
Listen to the mixing of each cosine frequency as you play with it in the first patch. It's just a 3 partial additive synth with equal volumes for each harmonic. You can get them to line up in nice harmonic relations to make simple organ or flute sounds or close together to get inharmonic beating for bell and metalic sounds.
Now listen to the effect of clipping the sum of these waves. Notice how the sound is richer, much less pure. There are new harmonics made by the clipping function. Because of clipping each wave interacts with the other two and they become "split" in a process of intermodulation. Intermodulation is a bit like ring modulation in its effect. Instead of pure sum and difference harmonics at f1-f2 and f1+f2 we get a series of integer multiples at 2*f1-f2, 3*f1-f2... n*f1-f2 in which extra harmonics related to both components appear. How many of them appear, in other words how big can n be? That depends on the non-linearity of the thing doing the intermodulation distortion, the waveshaping part. In this case the [clip~] unit is brutally non-linear, it just cuts the top and bottom off the time domain waveform, so we get a bit of all the harmonics in the series. As you can imagine, with three sine waves we can get a pretty complex signal using distortion to deliberately create intermodulation components. Puredata file .pd

The second shaping stage is the [cos~] unit preceded by an offset [+~] to add a fixed value to the signal. Used this way the cosine operator can be a balance between odd and even harmonics. We center the signal around zero and add a value of between 0 and 0.25 to get an odd/even balance. Puredata file .pd

We tend to want signals that have at least one very strong component, or fundamental frequency. If we take f1 as our fundamental and tap off a little extra bit of that to be added to the mix we get f1 as the strongest part. If we now pass the signal into another shaping function it focuses the pitch on that dominant part. But the problem is that if we move the f1 slider its harmonic relationship to f2 and f3 changes too, which alters the spectrum. Puredata file .pd

Making f2 and ,f3 follow f1 is the next step. Instead of being independent controls we have reassigned the faders for the oscillator frequencies as FO1 and FO2, for frequency offsets which we would normally add to the value of the fundamental. Here we've multiplied them by the fundamental instead, so we have a linear relationship of 2 harmonics which changes with the fundamental. Normally this isn't very musical, because the timbre changes massively with pitch still, but we are creating an "unreal" synthesiser here and want to explore this sound. Puredata file .pd

To turn this rather horrible complex spectrum generator into some semblence of a music synthesiser we need to add an envelope generator for the amplitude. Our target sounds are bright plucked or struck noises, so we employ the very simplest of envelopes, a decay envelope. We only have one control for the decay time and the attack is fixed at 1 ms for an almost instant start. The slope of the envelope generator is also very fast, by taking the 4th power we make a quite sharp and dynamic contour. Puredata file .pd

To test out this very basic experiment we need to build a little test bed. A random number generator inside the random midi notes subpatch picks out a few likely notes from a scale and we have added a [mtof] converter to the synth and subpatched its guts out of the way. You can play around with the sliders while the note generator runs to experiment with settings on the synth. Puredata file .pd

What is lacking with the sound is any change is spectrum as it decays away. Below is highlighted a small change in the synth DSP which multiples the second and third components by different powers of the envelope, this makes the sound most complex at the start of the envelope but the third, second and fundamental parts decay away at different rates. Puredata file .pd

Making a very simple graph-on-parent version of the synth containing its own controls tidys up things. We aren't sure we want to build an eleborate interface at this stage yet, we may decide that something so simple as this is to be used as a part of a bigger synthesiser. That is why Pd has such useful ability to hide interfaces in abstractions or subpatches and move them up or down to another layer just by cutting and pasting.

In this example we've taken a slightly different approach to sending and storing parameters. Each fader is still set up to broadcast to a unique message destination by filling in its send and receive symbols. We still use a list stored in a message box, but instead of routing named parameters as pairs they are stored in one big list which is packed or unpacked as needed. This means the order of the list matters now, otherwise parameters will end up at the wrong destination when the list is unpacked.

These can be copied to make little patch grabber/loader units each with it's own store and load button. It's not the most flexible idea, but it is simple and easy to understand and has it's own little advantages if you don't have many patches to choose from but want them saved per patch/song, like when building many songs/projects using the same abstractions, it's best to have them managed (loaded and saved) outside the abstractions so that settings are for the project. We will look at more elaborate load/save systems for parameters later on. Puredata file .pd

go to the next page More