The next kind of synthesiser to build lets us explore tables. Tables are a view of an array, and you can associate a graph with an array in Pd so that you can see its contents, or modify them directly. Data stored in an array can be used for almost any purpose in Pd, as the waveform for an oscillator, as transfer function for shaping and distortion, or as raw data for any other use. It's more complex than the previous examples but it has potential for making more realistic or unusual sounds.
There is no single synthesis method here, most good synthesisers are a collection of different techniques each used in its proper place. This architecture is quite easy to understand, we begin with an additive oscillator to give us a static spectrum and then modulate that with a slow moving envelope to get a loudness curve. At this point we have a sound rather like the Casio or Yamaha porta keyboards, very simple approximations of instrument timbres like flute, piano, guitar and sax. To add spectral shape we now feed that into a Chebyshev waveshaper. That will give us sone harmonics that depend on the amplitude of the signal. Finally we pass it through a soft distortion and formant filter to get a more natural body sound.
We begin in classic style with a [phasor~]. This will be our main signal reference. We pass that to the patch shown here. This is a table read using [tabread4~] to access the array. Each sweep of the phasor will read out a single period of the contents of table t0 and pass it on to the outlet. The code to set the contents of the table is shown next to it. To experiment with different tones I've set up 8 faders so you can tweak the additive components. 8 values are packed into a list which is given to the message template [sinesum 256 $1 $2 ...( and sent to the table with all the values substituted in place. After a short delay of 2ms the table is normalised too. Tables array values are stored in the little box called [graph], to see the contents you have to toggle on the "graph on parent" box in its properties. Hear the different timbres/tones of a bunch of preset spectra in the example pd file. Puredata file .pd

Waveshaping is distortion, and we use our signal as the index into a table that has a non-linear function stored in it. How do we make these tables? the table shown as array0 here is filled using an [until] loop. This is actually a dangerous way to use [until] in Pd, but as long as you never disconnect the righthand "stop" inlet it is safe. The halt condition is the box [sel 258] which sends a bang back to stop [until] when the counter hits 258. Pd likes tables to be a power of 2 plus 3, they don't have to be, but interpolating oscillators like [tabosc4~] work without any clicks if you observe this rule. For each index in the table (which appears on the righthand inlet of [tabwrite]) we write a value by sending it to the lefthand inlet of [tabwrite]. The table y-axis ranges from -1 to +1, as we want for audio signals, so to get a linear ramp in a 256 step table all we do is divide the index by half the range and subtract one.
Puredata file .pd
Banging the counter for array1 will fill it with a cosine if we normalise the range and then take the function y = 2 * pi * cos(x), and in the following example you can see how to compute y = 4*x^^3 - 3*x and how unweildy it can get using atomic operations at times. Expressions really come to the rescue here and make much neater code, the last panel showing the same polynomial created using [expr].
If the table contained a straight diagonal line, mapping one to one, two onto two etc, then the output would be the same as the input. But if we fill the table with a twisted curve, the output will be a distorted version of the input. By applying a non-linear function to a signal we obtain new harmonics. Like FM and AM the harmonics in the output depend on the harmonics in the input. We only ever get more. So starting with a single sine wave we could build up a spectrum, or as we are doing here we could start with a more complex initial spectrum and use that. Each component in the spectrum of the oscillator will be treated by the waveshaper.
Puredata file .pd
Unlike AM and FM the strength and position of the new components depends on different criteria when we do waveshaping . No longer is there a modulating signal, the modulating signal is stored independently of time in our table. What determines the harmonics now is the curve in the table and the amplitude of the input signal. Let's deal with the last of those first. The amplitude is moving because we decided to follow our oscillator with a loudness modulator and simple envelope, and suppose the table contained an almost perfect diagonal line, except for a bit near the top where it wobbles. If the oscillator signal is at about half scale loudness the wobbly bit of the graph never affects the output signal. The input sits in the linear region of the function. Once it grows louder parts of the input signal map into the distorted area of the table, and so some harmonics are introduced. As you can see the loudness of them will depend on the loudness of the original signal. That's a good property because in the physical realm many sound production mechanisms have this natural property.
Returning to the first reason, the shape of the curve now and skipping a lot of difficult theory, there are a bunch of functions called Chebyshev polynomials that are very useful to us, they allow us to add a single new haronic as a function of amplitude. Most functions give us a mixture of components, but Chebyshevs are the most basic tool if you like. What are they? Well imagine that you have a sinewave as your signal and a linear diagonal in the table. The result is a sinewave. So a linear diagonal represents a sort of null function, something that just passes the input along to the output, we call it the "identity". If you replaced the signal with a phasor, which is just a time signal version of the linear diagonal, and put a sinewave cycle in the table what would you hear? Exactly the same, swapping the sinewave and phasor positions between the table and the driving signal are equivilent. Hmm, this should tell us something*. Is there a function that when given a sinewave signal produces a single copy at another frequency? Well, we know we can double using y=x^^2, but what if we want a 3rd or 4th harmonic? There are a series of Chebyshev polynomial curves, a sequence of them defined recusively so that as they increase the bigger the higher order coefficients. The 0th one is always 1, and if the first CP = x then the next CP(n+1) is 2*x*CP(n) -CP(n-1). Each one is a distortion of a normalised line such that if you feed it with a sinewave as its range, the domain you get is the sinewave plus a new component which depends on the number of the Chebyshev polynomial, and the amplitude of the input signal of course. The nth CP produces the n+2th harmonic component. If we combine CP waveshapers and vary the amplitude of the incoming signal we can get new harmonics moving in and out. That's very cool, because with AM and FM remember how we could only deal with series of new harmonics rather than individual odd and even ones? Well now we can add in whatever we like at a whim. If we cascade waveshapers very carefully we can get an explosive multiplicationof harmonics at a certain point, which is great for brass. I've chosen to keep it simple in this synth and use a few shaping functions, just for the 3rd, 4th, 5th and 6th in the final example. Combined with using the additive source we can get some quite good bass, brass and woodwind sounds. Here's a simple take on waveshaping to show how this part of the synth works.
Puredata file .pd
For midi keyboard performance we want an envelope that can hold a note at a sustained level and then switch it off when the note is released. Until now we have used one-shot percussion envelopes like [ead~], but now we will use [adsr] which is a little more complex. The envelope generator takes 4 parameters, an attack time, a decay time, which are both in milliseconds as usual, and an extra sustain level, which is in percent, and release time in milliseconds. The attack and decay will always play, unless interrupted by a note off, followed by a sustain at the given level until the note-off is recieved when the release part of the envelope will play. During the release time it will fall from whatever the current sustain level is to zero. The [adsr] included here is from a helpfile example abstraction and is well commented.
We want a little bit of all the signals we are making. One directly from the main [phasor~] is taken through [cos~] to give us a fundamental sine. Next we take a mix from the output of the additive oscillator, and finally a mix from each waveshaper. These are paired 3rd and 5th, 4th and 6th, because often one wants to choose between odd and even series to get the right harmonic balance for a sound.
Finally we have a body filter, to help more realistic guitar and string sounds. Instead of normal [bp~] units, this time we have used [biquad~] units which are more flexible but harder to set up. We want a resonant body but with bands that don't interfere with each other where they overlap, and the biquad~ gives us that better phase characterisic. They are harder to set up because each filter must be programmed for specific behaviour, but luckily we don't have to calculate the filter coefficients here which would be annoying, little helper units are there for us called [bandpass], [highpass], [lowpass] and [notch]. I've put the bands in ranges 0-300, 300-700, 600-1100, 900-1400, which are a compromise to allow the synth to emulate dark stringy sounds as well as brighter brass sounds.
It's not the prettiest GUI in the world, but it does the job for us. The problem with vertical sliders vs horizontal ones is that it's easier to write a name over the horizontal type, with our interface we can only afford room for one letter denoting the function of the slider. Going left to right these are vibrato, attack, decay, sustain, release, mix1 (fundamental), mix2 (additive oscillator output), mix3 (waveshaper1), mix4 (waveshaper2), formant1, formant2, formant3, formant4, mix of direct and formant filtered signal, soft distortion (Clip) amount. Wow, that's actually quite a few to manage, as well as the 16 possible preset oscillator settings on the radio button bar along the top. But it's a compact and versatile little arrangement as you will find. The two toggle buttons on the right set the Chebyshev tables to use, either C3/C5 and either C4/C6.

Looking inside the [store and recall] unit for a moment, see how we've basically reused the list saver/loader things from the last patch. Again we have a limited set of program positions to store things in, here we have a bank of 8 presets. The currently selected patch is set by the number on the interface. If "store" is pressed at any time the state of the interface will be written to the currently selected patch. Changing the currently selected patch automatically loads the interface settings for it. This is simple and quite powerful but has one big disadvantage other than not yet letting us save and load banks from a file, if we didn't want to write over the currently selected patch and we change selection we lose all the settings. So we can only save over the patch we select. This is something to improve on in the next synth.

The synth core already responds to "velocity", and we scaled that to be in 0-127 range. And we took care of note offs by making the [adsr] unit shut down and release when the velocity is zero. All we need to do to hook this up as a midi monosynth is to add a [notein] object and [pack] the (note, velocity) pair into a list for the synth.
Puredata file .pd
As it stands our creation works as a monosynth. But for brass or piano sounds we want more than one instrument playing at once. The time has come to make an abstraction of the voice. Before making it an abstraction we want to decouple the code as much as possible from the interface. That means no messages will have to travel back and forth as global variables, because that is bad. Instead, each synth voice will now receive all its parameters through a single list along with its note number and velocity. The method we will use is to add a poly object and route each played note to a copy of the voice. To make the best of CPU resources we have added a [switch~] block to the voice abstraction so it shuts down when silent. The annoying thing is that our synth now consists of two puredata files, there isn't yet a way to embed the abstraction into the file so you'll have to download all the following files and put them together in the same synth directory. The great news is you can now instantiate as many copies of the synth as you like in a composition without having to worry about any names clashing and variables getting overwritten.
synth voice Puredata abstraction

* find out more about waveshaping - The early gurus of waveshaping are probably Daniel Arfib, Marc LeBrun papers , James Moorer and some other scientists like Grey and Wessel who did the groundwork in the 1970s and 1980s. Amongst other things it is demonstrated that basically waveshaping and FM are equivilent things and how the unit cycle develops into a theory of "wavelets" which has an important role in granular synthesis.