[
Cottle examples]
8. Additive or Fourier Synthesis, Random Numbers, Debugging and Postln, CPU usage
//8.1 message chains
[30, 23, 87].choose; //choose one member of the array
20.rand; //choose a number between 0 and 20
71.midicps; //convert the midi number to cycles per second (Hz).
//Now all of them combined in several lines of code.
a = [30, 56, 32]; //store array in variable a
b = a.choose; //choose a value from a and store it in b
c = b.rand; //choose a random number from 0 to b
d = c.midicps; //calculate the cps given c as a midi number
d //post the results
// .postln is now the default behavior of the interpreter. In sc2,
// you would have to state .postln explicitly to see the results.
// sc3 posts the results of the last expression automatically.
//8.2 message chain
[30, 56, 32].choose.rand.midicps;
// using postln causes the result to be printed twice. Why?
// .postln prints it once, and returns the result to the interpreter.
// Then the interpreter prints it.
[30, 56, 32].choose.rand.midicps.postln;
//8.3 Wavetable
// Wavetable editor has not been implemented in sc3 yet.
// However, wavetables can be used. A buffer has to be allocated
// and filled using methods similar to Signal methods in sc2.
s = Server.internal;
s.boot;
(
var buffer, har, harmArray;
buffer = Buffer.alloc(s, 2048, 1); // 2048 sample buffer
harmArray = Array.series(24, 1, 1);
buffer.sine1(1 / harmArray); // reciprocal of [1..24]: sawtooth partials
{ Osc.ar(buffer.bufnum, 200, 0, 0.5) }.scope;
)
//8.4 normalize
[1.0, 0.44, 0.49, 0.16, 0.38, 0.59,
0.20, 0.03, 0.11, 0.06, 0.47].normalizeSum;
//8.5 random spectra
(
{ 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;
)
//8.6 bell
(
{ 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;
)
//Random Numbers, Perception
8.7 rand
//10.rand;
10.0.rand;
//8.8 test array
var testArray;
testArray = Array.fill(12, {100.rand});
testArray;
//8.9 function error
var testArray;
testArray = Array.fill(12, 100.rand);
testArray;
//8.10 random seed
var testArray;
thisThread.randSeed = 5;
testArray = Array.fill(12, {100.rand});
testArray;
//Bell Array
//8.11 random frequencies
(
{
var harmArray, ampArray, fund; //declare two variables
//in the first array store 12 values between 1 and 5.0
//in the second store 12 values between 0 and 1.0,
//normalize the second array (ampArray) so as not
//to exceed 1.0
fund = 200;
harmArray = Array.fill(12, {1 + 4.0.rand});
ampArray = Array.fill(12, {1.0.rand}).normalizeSum;
Mix.ar(SinOsc.ar(harmArray * fund, 0, ampArray));
}.scope;
)
//Debugging, Postln, Comments
//8.12 postln, post
// What happens when you do .postln or other client-side activities
// within a ugenfunc or SynthDef function?
// New users of sc3 often expect that the server will do those things
// whenever the synth is started on the server, or that they will be
// executed repeatedly while the synth is playing. Not true.
// They are executed once, when the ugenfunc is evaluated. Then
// they are forgotten.
(
{
var harmArray, ampArray, fund; //declare two variables
//in the first array store 12 values between 1 and 5.0
//in the second store 12 values between 0 and 1.0,
//normalize the second array (ampArray) so as not
//to exceed 1.0
fund = 200;
harmArray = Array.fill(12, {1 + 4.0.rand});
ampArray = Array.fill(12, {1.0.rand}).normalizeSum;
harmArray.postln;
ampArray.postln;
Mix.ar(SinOsc.ar(harmArray * fund, 0, ampArray));
}.scope;
)
//8.13 3 bells
// As in the crotales example, we can't pass the frequency arrays
// into the synth at play time. Therefore, we must create a separate
// synthdef per frequency array.
// Make the synthdefs.
(
var fArray1, fArray2, fArray3, aArray;
var env;
//declare variables and fill the arrays with random values
fArray1 = Array.fill(12, {1 + 4.0.rand});
fArray2 = Array.fill(12, {1 + 4.0.rand});
fArray3 = Array.fill(12, {1 + 4.0.rand});
aArray = Array.fill(12, {1.0.rand}).normalizeSum;
//the perc envelope uses only an attack and a decay
env = Env.perc(0.0001, 2);
[fArray1, fArray2, fArray3].do({ |freqs, i| // or: arg freqs, i;
SynthDef("ch8-bell" ++ i, {
// again, [0, 1] here expands a mono signal into stereo
Out.ar([0, 1], Mix.ar(
SinOsc.ar(
freqs * 200,
0,
aArray
)
)*EnvGen.kr(env, doneAction:2));
// spawn is removed, as it is meaningless inside a synthdef
}).send(s);
});
)
// Now, the spawn routine.
(
r = Task({
// the 3.rand chooses one of the synthdefs randomly
// alternate syntax: Synth.new(["ch8-bell0", "ch8-bell1", "ch8-bell2"].choose, target:s)
inf.do({
Synth.new("ch8-bell" ++ 3.rand, target:s);
3.wait;
});
}).play(SystemClock);
)
r.stop;
//8.14 arrays and math
a = [1, 5, 7, 3, 8];
(a + 34);
(a*1.23);
//Random Bell Patch
//8.15 random bells
// essentially, this must take the same form as 8.13.
// here I combine the synthdef creation and playback routine into
// the same code block. Note the beginning of the routine.
// Random bells
(
var fArray, aArray, totalBells = 6, baseFreq = 400, totalHarm = 12;
var env, bell, nextEvent = 1.5, envAttack = 0.0001, envDecay = 1;
//create an array of arrays and store it in fArray.
//totalBells will determine the number of possible freq arrays.
//fill each array with 24 random ratios between 1.0 and 5.0
fArray = Array.fill(totalBells,
{Array.fill(totalHarm, {1 + 4.0.rand})})*baseFreq;
aArray = Array.fill(12, {1.0.rand}).normalizeSum;
env = Env.perc(envAttack, envDecay);
fArray.do({ |freqs, i| // or: arg freqs, i;
SynthDef("ch8-bell" ++ i, {
// again, [0, 1] here expands a mono signal into stereo
Out.ar([0, 1], Mix.ar(
SinOsc.ar(
freqs * 200,
0,
aArray
)
)*EnvGen.kr(env, doneAction:2));
// spawn is removed, as it is meaningless inside a synthdef
}).send(s);
});
r = Task({
inf.do({
// by starting with the .wait, I ensure there will be enough time
// for the server to receive the synthdefs. Try reversing these
// two lines. What is the result? Why?
nextEvent.wait;
Synth("ch8-bell" ++ totalBells.rand, target:s);
});
}).play(SystemClock);
)
r.stop;
//CPU Usage
//8.16 CPU usage
(
var fArray, aArray;
fArray = Array.series(100, 1, 1);
aArray = (1/fArray).normalizeSum;
{ Mix.ar(SinOsc.ar(fArray*200, 0, aArray)) }.scope;
)
{ Saw.ar(200, 0.5) }.scope;
//Expanded Examples
//8.17 harmonic spectra
{ Blip.ar(100, LFNoise0.ar([20, 21], 10, 11), 0.3) }.scope;
//8.18 inharmonic spectra
(
var freqArray, env;
env = Env.perc(6, 0.1, 0.2); //envelope: (attack, decay, level)
//level should not exceed 1.0
// this will demonstrate how to pass a frequency into a synthdef at play time.
// A very common operation when playing notes at a specific pitch. Rather important.
SynthDef("ch8-fun", { arg freq, pan;
Out.ar(0, Pan2.ar(
SinOsc.ar(
freq,
mul: EnvGen.kr(env, doneAction:2)
//mul SinOsc by these values
//there is a .75 percent chance that
//the value is 0, therefor no sound
),
pan)
)
}).send(s);
r = Task({
inf.do({
// if we choose a nonzero value, play the synth.
// this is effectively the same as Cottle's spawn, except
// without wasting ugens by creating a synth with amplitude 0.
if([0, 0, 0, 0.1].choose > 0,
// synth.head makes sure the sound happens before the scope
// see the [Order-of-execution] helpfile for more details.
{ Synth.head(s, "ch8-fun", [\freq, 600 + 1000.rand, //random freq between 600 and 1600
\pan, 1.0.rand2]) }
);
0.3.wait;
});
}).play(SystemClock);
s.scope;
)
r.stop;
// try substituting the following, shorter syntax for the if call above. Equivalent in functionality.
// if(0.25.coin, // 1 in 4 chance of returning "true"