14 -Additive Synthesis, Random Numbers, CPU usage 

Why, after nearly 50 years of electronic music and instrument design, can we still tell if an instrument is synthesized, even with sampled instruments? What most synthetic models lack are complexity and chaos. Even an exact duplication of a single note from any instrument is not a true representation of that instrument, rather a single instance of many possibilities. A single high resolution digital photo of a leaf duplicated hundreds of times would not look like a tree. All the leaves on a tree are different, and they change in real time. The same is true for a piano. Each key has its own harmonic fingerprint. Even the same key when struck successively will produce different partials depending on the position of the vibrating string when it is struck. Real instruments are complex and chaotic. SC is the first real-time synthesis package I've encountered that is capable of, even invites and encourages this level 
of complexity. 

On early synthesizers composers experimented with additive synthesis (combining individual sine waves to generate more complex waveforms) using four or five oscillators and maybe two or three envelope generators. Of course they couldn't come close to natural sounds because a trumpet could have 16 partials, each with its own real time (and chaotic) envelope. But having SC on your laptop is like a warehouse of synthesis VCOs and envelopes at your disposal. With only a few lines of code you can create hundreds of partials with individual envelopes. But let's start with six. 

14.1. additive synthesis model (Mix.ar, EnvGen, Env, SinOsc) 

(
{Out.ar(0, Mix.ar(In.ar(4, 6))*[0.4, 0.4])}.play;
{
var gate, fund;
gate = MouseButton.kr(0, 1, 0);
fund = MouseX.kr(100, 1000, warp: 1);

Out.ar(4, [
SinOsc.ar(fund*1)*EnvGen.kr(Env.adsr(0, 0, 1.0, 0.5), gate, 1.0),
SinOsc.ar(fund*2)*EnvGen.kr(Env.adsr(0, 0, 1.0, 0.5), gate, 0.5),
SinOsc.ar(fund*3)*EnvGen.kr(Env.adsr(0, 0, 1.0, 0.5), gate, 0.33),
SinOsc.ar(fund*4)*EnvGen.kr(Env.adsr(0, 0, 1.0, 0.5), gate, 0.25),
SinOsc.ar(fund*5)*EnvGen.kr(Env.adsr(0, 0, 1.0, 0.5), gate, 0.2),
SinOsc.ar(fund*6)*EnvGen.kr(Env.adsr(0, 0, 1.0, 0.5), gate, 0.166)
])
}.scope(10, zoom: 4)
)

This patch adds together six sine waves tuned to harmonic partials (f*1, f*2, etc). The MouseButton object is similar to MouseX, returning the first value (0) when not pressed, the second (1) when pressed and for as long as it is held; perfect for a gate (except I have to be careful where I click the mouse!). Don't worry about the Out and Mix.ar for now. Each sine wave is playing back on a different bus (4 through 9, 2 and 3 are inputs so we aren't using them), and they are mixed together to L/R speakers of your computer. You hear the top wave, which is all the others added together. In the EnvGen I use the levelScale (third argument) to adjust the relative volumes of each upper partial. 

Try changing the multiples of each SinOsc freq argument (f*3, f*5, etc. or f*2, f*4, etc.). Change the last argument in each of the EnvGen (levelScale). Note how the timbre changes. Also notice that you don't really hear any of the individual sine waves. You just hear a single bright pitch. Try replacing the levelScale of one of the envelopes with a MouseY.kr to see how that single harmonic changes the sound. You may find that when the harmonic first changes you hear that single sine wave, because it is brought to your attention. But the longer you listen the more it blends in with the other sounds. 

Next try changing the attacks, decays, sustains, and decays so that each upper partial has a different envelope. With these few dozen lines of code we have already surpassed the capacity of most commercial synthesizers. Now try adding more harmonics. (Note that the last SinOsc does not have a comma, because it is at the end of the array list. If you use copy and paste, copy the second to last SinOsc line and paste it before the last one.) Granted, this exercise is a bit tedious, perhaps giving you a glimpse of the pioneering efforts of early electronic composers working on patch style synthesizers. 

But there is always an easier way to do things in SC. Its limitations are often in the person writing the code. Another version of the patch below uses Array.fill to do all the typing, so to speak, for us. Array.fill generates an array based on a function. The first argument is the number of items in the array and the second is the function. Within the function is a new syntax: an argument generated by the Array.fill that can be passed to the function. It is a counter that keeps track of each iteration as the array is built. It can be named anything you like. In this case I call it counter. Run each of the lines below and check the results in SCs data display window. 

14.2. Array.fill (rand, choose, arrays, midicps, postln) 

//fill an array with the number of this iteration
Array.fill(16, {arg counter; counter})

//fill an array with random numbers
Array.fill(16, {arg counter; rrand(20, 100)})

//fill an array with multiples of counter
Array.fill(16, {arg counter; counter*3})

//fill an array with SinOsc objects, each with a freq of the counter*100
Array.fill(16, {arg counter; SinOsc(counter*100)})

The counter can be used to calculate the freq of each SinOsc and the levelScale of each EnvGen. The counter begins at 0, not 1, so I use the variable partial to increase it by 1. The code below generates 16 upper partials, each proportionally softer, with a random decay between 0.1 and 2.1. The fund*partial ensures they are all harmonic. The 1/partial is the levelScale for the envelope. The first will be 1/1, the next 1/2, 1/3, 1/4, etc. Array.fill returns an array of SinOsc ugens and Mix.ar mixes them all down to one channel. 

14.3. additive saw wave (rand, choose, arrays, postln) 

(
{
var gate, fund;
gate = MouseButton.kr(0, 1, 0);
fund = MouseX.kr(50, 1000);
Mix.ar(
	Array.fill(16,
	{arg counter;
	var partial;
	partial = counter + 1;
	SinOsc.ar(fund*partial) * 
	EnvGen.kr(Env.adsr(0, 0, 1.0, TRand.kr(0.2, 2.0, gate)), 
		gate, 1/partial)
	})
	)*0.2 //overall volume
}.scope(1)
)

I began this section praising SC's capacity for complexity and richness. The patch above isn't that astonishing, but you should recognize the more natural sound in the different decay rates for each partial. Compare it with this patch, which uses only one envelope for all partials, similar to most synthesizers. 

14.4. additive saw wave (rand, choose, arrays, postln) 

(
{
var gate, fund, env;
gate = MouseButton.kr(0, 1, 0);
fund = MouseX.kr(50, 1000);
env = Env.adsr(0, 0, 1.0, 2.0);
Mix.ar(
	Array.fill(16,
	{arg counter;
	var partial;
	partial = counter + 1;
	SinOsc.ar(fund*partial) * 
	EnvGen.kr(env, gate, 1/partial)
	})
	)*0.2 //overall volume
}.scope(1)
)

For a more complex example of the richness afforded by SC, see the "flashing" patch at the end of the chapter. 

Each of the sine waves in this example is a multiple of the fundamental. In this case the multiples are 1, 2, 3, etc. up to 16. This is a harmonic spectrum. Most pitched instruments have harmonic spectra, but many things have inharmonic spectra, or a set of frequencies that are not multiples of the fundamental. To generate an inharmonic spectrum you need to enter unrelated values for each sine wave. I got out my ruler and carefully measured the lines from Pellman's text on figure 7.3 illustrating inharmonic spectra to guestimate those frequencies. I came up with 72, 135, 173, 239, 267, 306, 355, 473, 512, 572, and 626. These numbers are inharmonic because there is no common multiple or pattern. The amplitudes on figure 7.3 are approximately 1.0, 0.44, 0.49, 0.16, 0.38, 0.59, 0.20, 0.03, 0.11, 0.06, and 0.47. These amplitudes, if added together, would total more than 1.0. Earlier I pointed out that you should not exceed 1.0 in the multiply argument of a sine wave, otherwise you'll get a distorted sound. If we used the amplitude array the way it is in this chart it would be too loud and we would loose harmonic information. All of the values need to be reduced proportionally such that they total 1.0. This is called "normalize." To do this manually you could add all the values, divide each value by that total, and so on, but there is a message in the SC that will save us the calculation. Here is the code. 

14.5. normalizeSum 

[1.0, 0.44, 0.49, 0.16, 0.38, 0.59, 
	0.20, 0.03, 0.11, 0.06, 0.47].normalizeSum;

I will round off the results to this: 0.25, 0.11, 0.12, 0.04, 0.1, 0.15, 0.05, 0.01, 0.03, 0.02, and 0.12. 
In SC the code for generating the spectrum illustrated in the book would be: 

14.6. inharmonic spectra (scope, Mix, SinOsc, arrays) 

{Mix.ar(
	SinOsc.ar(
		[72, 135, 173, 239, 267, 306, 355, 473, 512, 572, 626],
		0, //phase
		[0.25, 0.11, 0.12, 0.04, 0.1, 0.15, 0.05, 0.01, 0.03, 0.02, 0.12]
	))}.scope(1)

Can you identify this sound, maybe a ringing gong, or large bell? Any set of random frequencies would result in a similar sound. 

How would we come up with our own set of random harmonics? We could use a deck of cards, with a rule that we are picking frequencies between 111 and 999, ensuring choices with three digits that only use integers between 1 and 9, drawing three at a time and use each set of 3 cards for a frequency. We ignore face cards and 10s. For the amplitude array we can do the same, but one value per card. This time I'll limit the number of values to 8. 

This is what I got: 986, 329, 875, 498, 754, 476, 332, 354 for pitch and 0.6, 0.7, 0.4, 0.5, 0.7, 0.8, 0.9, 0.4 for amplitude. In lines 37 to 43 the normalizeSum message is included in the code. 

14.7. bell (Mix, SinOsc, arrays, normalizeSum) 

(
{Mix.ar(
	SinOsc.ar(
		[986, 329, 875, 498, 754, 476, 332, 354], 
		0,
		[0.6, 0.7, 0.4, 0.5, 0.7, 0.8, 0.9, 0.4].normalizeSum
	))}.scope(1)
)

It sounds close to the example from the book, maybe a sci-fi sound track. Do you think you could distinguish between the two examples if they were played back to back? I was very surprised at how easy it is to tell them apart even though the harmonics were chosen supposedly at random. I'll demonstrate that below. But first, we need to discuss random numbers. (This is a somewhat theoretical discussion that is not essential to synthesis, so you can read it once and move on.) 

Random Numbers, Perception 

There is no such thing as a random number. "Random" is a human concept based on our ability or inability to perceive a pattern. (Many composers use the term "pseudo-random" when referring to random processes.) Take, for example, a deck of cards. When I shuffle them they are "randomized" so that I can't predict what the sequence of cards would be. But if I knew the order of the cards before the shuffle, and I used a precise formula (e.g. exactly one from each pile in turn), or kept close track of the order in which the cards fall during shuffling, I would then be able to predict the order. If that were the case, the deck would not be randomized, but perfectly predictable. What is the difference? It's only my level of perception. So what random really means is mixed up to the point where a particular human can't predict the outcome. (Card tricks work on the principle that the one being tricked perceives the deck as randomized. To the one doing the trick, it is not.) 

To generate random sequences composers have resorted to a sort of number shuffling using a formula (see Moore page 409). The computer "knows" the sequence, because it is a formula, but the formula is so complex that we don't recognize the pattern. Computers use a random number generator, which is a (very long) series of numbers created each time the program is run. The odd thing is that the order never changes because the same formula is used each time; the set of numbers rest in the computer's memory intact. So to the computer it is not random at all. It's like a deck of cards that has been shuffled once, but is never shuffled again. When you run a program that generates supposedly random events (such as a dice or card game), the computer looks for values from this sequence of numbers. The problem is it always starts at the beginning, so you always get the same values, like using the same deck of cards from the top of the deck without cutting or reshuffling. The order will seem randomized the first few times through the series, but will never change. After two or three repetitions we will remember the pattern and it won't be random anymore. Part of random, to us, is different numbers each time. So how do you get it to do different numbers? You cut the deck. In the number generator you start at a different point each time. This is called a random seed. You give the computer a number as a seed which is used to count into the random number sequence. The computer then starts from that point. 

But if you know the number, it's not really random. In addition, entering a number each time is a hassle. Somehow you have to find a "random" seed (perplexing?). The solution is to use the internal clock of a CPU, which is just a series of numbers rapidly flying by. If you grabbed a number from the clock when you run the program you couldn't predict where the clock would be at that precise moment. It seems a little convoluted, but that's how it was (and is) done. 

SC does a random seed automatically behind the scenes. Each time you run a line like 10.rand SC first reads the number on its internal clock to use as a seed. It then moves into the random number generator sequence and starts its sequence of choices from that point. It is random to us because we can't predict what number the clock gave or the order of the numbers at that point. 

Why are seeds important? When you want to reproduce the same series of events. (Some card games allow you to replay that hand. This is how it's done.) There are cases where you may want a predictable seed and predictable events. Each seed number represents that version of pseudo-random numbers. If you give the same seed twice the code will generate precisely the same values on subsequent runs. The events will seem random in the sequence, but it will be the same pseudo-random sequence. This is useful when you are debugging and want to reproduce an error. Also you will find a particular variation of random events that you like and will want to reproduce it exactly. 

First I'll demonstrate some random choices and then some random choices using a seed. To generate a random number you use the message rand sent to any number. It will return a random choice between 0 and that number. 55.rand will return an integer (e.g. 1, 2, 3) between 0 and 55 (not including 55). 55.0.rand will return a floating point number (e.g. 3.3452, 1.2354) between 0.0 and 55.0. Try both lines several times each to see how random numbers are chosen. 

14.8. rand 

10.rand;

10.0.rand;

Running the lines several times over and over is a bit cumbersome, so below is a method of testing random choices using the dup message, which fills an array with duplicate returns of the function. 

14.9. test array (rand, dup) 

{100.rand}.dup(20)

Here is a typical beginner error. Try running the example below and see how different the results are. 

14.10. function error 

(100.rand).dup(20)

It picks a random number, but it uses that same number each time. The first example uses a random number choice enclosed in a function. A function means "run this line of code" while 100.rand on its own means pick a random number. {100.rand} means pick a random number each time, 100.rand means pick a random number and use it each time. 

Below is the same example using a seed. Run this set of lines four or five times and notice that we do get a random array, but it's the same array each time. Try changing the seed to something other than 5. You'll get a new series, but the same series each time. (Not very "random" after four runs.) 

14.11. random seed (thisThread, randSeed) 

thisThread.randSeed = 5;
{100.rand}.dup(20)

So now rather than thinking of your generative work as random or pseudo-random series of events, you can consider the code you write for a piece the DNA for billions of variations, of which one is chosen at each performance. Maybe life is like that too? 

Bell Array 

Earlier we discussed the tonal quality of a random set of upper partials or inharmonic frequency spectrum. It is easy to recognize the pattern of a harmonic spectrum, but could we distinguish between several sets of random or inharmonic partials? The truth is we are quite good at distinguishing a collection of complex sounds. This is why we can recognize a person's voice over the phone after knowing them only a short time. 

The code below generates a set of bell like sounds, each with it's own pseudo-random harmonic spectrum. Try reading it from the center out. The innermost SinOsc has a random frequency between 50 and 1000. It is multiplied by an envelope with a decay between 0.2 and 3.0 and a volume between 0 and 1. This code is inside a function with the dup message with 12 duplications. The dup message creates an array of SinOsc ugens with differing frequencies and envelopes. Since the trigger is created outside the function, all the Ugens share this single trigger. The attacks will be the same and it will appear as a single sound. 

Those 12 sine waves, or inharmonic frequencies, are mixed down using Mix.ar and panned with Pan2. Try running the first example four or five times. Can you tell that some frequencies have longer or shorter decay rates? Could you enter a line of code that would allow you to use a random seed and produce the same bell collection twice in a row? The second example fills and mixes an array of these bells (an array of an array). 

14.12. random frequencies (Pan2, Mix, EnvGen, Env, fill) 

(
{
var trigger;
trigger = Dust.kr(3/7);
Pan2.ar(
	Mix.ar(
		{
		SinOsc.ar(rrand(50, 1000)) * 
		EnvGen.kr(
			Env.perc(0, rrand(0.2, 3.0)), 
			trigger,
			1.0.rand
			)
		}.dup(12)
	)*0.03, 
	1.0.rand2
)
}.play
)

// Several bells

(
{
var bells, totalBells, totalHarm;
totalBells = 5;
totalHarm = 12;

bells = {
	var trigger;
	trigger = Dust.kr(2/7);
	Pan2.ar(
		Mix.ar(
			
				{
				SinOsc.ar(2000.linrand + 50) * 
				EnvGen.kr(
					Env.adsr(0, 0, 1.0, rrand(0.3, 2.0)), 
					trigger,
					1.0.rand
					)
			}.dup(totalHarm)
		)*0.03, 
		1.0.rand2
	)
	}.dup(totalBells);
Mix.ar(bells)
}.play
)

CPU Usage 

Now that we are pushing the limits of the processor the cpu information in the server window (shown below) is important. It shows average and peak cpu and number of Ugens. Run the patches above several times or increase the number of partials and see what happens when you max out the cpu load. 

Even on the fastest machines it's pretty easy to overload. That is because additive synthesis is very inefficient. There are better ways to achieve a bell, which I cover in later chapters. 

Move back to the single bell patch and continue to increase the partials of a single bell. What does 200+ inharmonic partials sound like? The metallic sound should become more and more unfocused and you eventually hear noise. That is covered in the next chapter. 

Practice 

14.13. flashing24 (MouseButton, Mix, Array.fill, Pan2, EnvGen, Env LFNoise1) 
Based on McCartney's patch in the examples folder

(
{
var trigger, fund;
trigger = Dust.kr(3/7);
fund = rrand(100, 400);
Mix.ar(
	Array.fill(16,
	{arg counter;
	var partial;
	partial = counter + 1;
	Pan2.ar(
		SinOsc.ar(fund*partial) * 
		EnvGen.kr(Env.adsr(0, 0, 1.0, 5.0), 
			trigger, 1/partial
		) * max(0, LFNoise1.kr(rrand(5.0, 12.0))), 1.0.rand2)
	})
	)*0.5 //overall volume
}.play
)

//Several of the above mixed down

(
{
var trigger, fund, flashInst;
flashInst = Array.fill(5,
{
	trigger = Dust.kr(3/7);
	fund = rrand(100, 400);
	Pan2.ar(
		Mix.ar(
			Array.fill(16,
			{arg counter;
			var partial;
			partial = counter + 1;
				SinOsc.ar(fund*partial) * 
				EnvGen.kr(Env.adsr(0, 0, 1.0, 5.0), 
					trigger, 1/partial
				) * max(0, LFNoise1.kr(rrand(5.0, 12.0)))
			})
			)*0.2,
	 1.0.rand2)
});
Mix.ar(flashInst)*0.6
}.play
)

14. Exercises 
14.1. Which wave shapes result from odd harmonics and even harmonics? 
14.2. Automatically fill an array with squares of the numbers 1 through 10. 
14.3. Automatically fill an array with reciprocals of the numbers 1 through 10. 
14.4. Rewrite the additive saw so that each partial is randomly detuned by +/-10%. 
14.5. Devise a method of ordering numbers and/or letters that would appear random to 
someone else, but is not random to you.