here two methods are shown, whereby the last one is the only reliable one
in terms of efficiency.
- [1] Monte-Carlo-Method
- [2] The integral table method
- Histograms
- Using an envelope as a distribution function
- Example of granular synthesis with drawing random distributions
- Server side random distribution
interesting links related to random distribution:
[1] Monte-Carlo-Method:
The area of the function is used to specify whether a random x-y-pair falls below or above (Monte-Carlo-Method)The distribution acts as a limit function and find values that meet this constraint.
The disadvantage is that the efficiency of this method might vary inexpectedly and even fail.
(
g = { arg irpow=0.6, size=100;
var x, y, a, min, max;
a = Array.fill(size, { arg i; abs(i)**((1 / irpow) - 1) }).normalizeSum;
min = a.minItem;
max = a.maxItem;
a = a.collect { |el| el.linlin(min, max, 0, 1) };
while {
y = 1.0.rand;
x = a.blendAt(size.asFloat.rand);
//x.postln;
x < y
};
x
};
)
g.value;
[2] Using an integrated function table
(why this works: see here)
(
f = {arg irpow=0.9, size=100;
var a, b, sum=0;
a = Array.fill(size, { arg i; abs(i)**((1 / irpow) - 1) });
size = a.size;
a = a.collect { |el| sum = sum + el }; // incrementally integrate
a = a.normalize(0, size-1); // divide by sum (maximum value) and scale by max index
b = Array.fill(size, { arg i; a.indexInBetween(i) }); // flip array
b = b / size // rescale to 0..1
}
)
z = f.value(0.9, 200);
z.plot;
// now this produces values of the desired distribution:
z.blendAt((z.size - 1).asFloat.rand);
Using a histogram, one can test if the results are appropriate:
(
var randomNumbers, histogram, maxValue = 500.0, numVals = 100000, f, z, dfunc, size=200;
//dfunc = { arg i; var pow=0.056; i = i / size; abs(i)**((1 / pow) - 1) };
dfunc = { arg i; i = i / size; (sin(i * 80) * 0.3 + sin(i * 23)).max(0) };
f = { arg func, size = 100;
var a, b, sum=0;
a = Array.fill( size, func );
a = a.collect { |el| sum = sum + el }; // incrementally integrate
a = a.normalize(0, size-1); // divide by sum (maximum value) and scale by max index
b = Array.fill(size, { arg i; a.indexInBetween(i) }); // flip array
b = b / size // rescale to 0..1
};
z = f.value( dfunc, size );
// plot a sample of the underlying distribution function
Array.fill( size, { arg i; dfunc.value( i )}).plot("distribution function");
// now this produces values of the desired distribution:
randomNumbers = Array.fill(numVals, { z.blendAt((z.size.asFloat - 1).rand); }) * maxValue;
z.plot("mapping table");
histogram = Signal.newClear(maxValue);
randomNumbers.do({ arg each; var count, histoIndex;
histoIndex = each.asInteger;
count = histogram.at(histoIndex);
histogram.put(histoIndex, count + 1)
});
histogram.plot("histogram for distribution function application 0 - " ++ maxValue);
)
For the function:
dfunc = { arg i; i = i / size; (sin(i * 80) * 0.3 + sin(i * 23)).max(0) };it results in the following relation between distribution function, scaling function and histogram of the results:

Using an envelope as a distribution function:
(
var randomNumbers, histogram, maxValue = 500.0, numVals = 100000, f, z, dfunc, env, size=1500;
env = Env([0, 1, 0, 0.3, 1], [1, 1, 1, 1].normalizeSum, [-0.4,8, 'step', 'lin']).plot;
dfunc = { arg i; env[i / size] };
f = { arg func, size=100;
var a, b, sum=0;
a = Array.fill(size, func);
a = a.collect { |el| sum = sum + el }; // incrementally integrate
a = a.normalize(0, size-1); // divide by sum (maximum value) and scale by max index
b = Array.fill(size, { arg i; a.indexInBetween(i) }); // flip array
b = b / size // rescale to 0..1
};
z = f.value(dfunc, 2000);
// now this produces values of the desired distribution:
randomNumbers = Array.fill(numVals, { z.blendAt((z.size - 1).asFloat.rand); }) * maxValue;
z.plot("mapping table");
histogram = Signal.newClear(maxValue);
randomNumbers.do({ arg each; var count, histoIndex;
histoIndex = each.asInteger;
count = histogram.at(histoIndex);
histogram.put(histoIndex, count + 1)
});
histogram.plot("histogram for distribution function application 0 - " ++ maxValue);
)
Example of granular synthesis with drawing random distributions
asRandomTable and tableRand are methods part of supercollider which implement the above algorithm.
// keep z and c as a global random table
// using the "l" key (when th plot view is in focus) you can unlock the plot view and draw into the array.
// have a look at the mapping table: it changes only a very little, but this change has
// a distinct effect on the distribution
(
var v, env, size=180; // keep w, z, d global
env = Env([0, 1, 0, 0, 1, 0], [0.3, 1, 1, 1, 0.2].normalizeSum, [-0.4,8, 'step', 'lin']);
c = Array.fill(size, { arg i; env[i / size] });
z = c.asRandomTable;
// views:
v = z.plot("mapping table");
w = c.plot("distribution function");
// now this produces values of the desired distribution:
f = { z = c.asRandomTable; d.value = z; };
// dig for multislider view.
w.view.children[0].children[1].mouseUpAction = { f.value; };
)
// create a synth definition for a short grain:
(
SynthDef(\pgrain,
{ arg out = 0, freq=800, sustain=0.001, amp=0.5, pan = 0;
var window;
window = Env.sine(sustain, amp);
Out.ar(out,
Pan2.ar(
SinOsc.ar(freq),
pan
) * EnvGen.ar(window, doneAction:2)
)
}
).store;
)
// play a task definition. Tdef(\x).play;
// this definition can be changed on the fly :
(
Tdef(\x, {
loop {
s.sendBundle(0.2,
["/s_new", \pgrain, -1, 1, 1, \freq, z.tableRand.linexp(0,1,200, 18000)]
);
0.03.wait;
}
});
)
Server side random distribution
// tuning randomness on the server with the above created randomTable
// using waveshaping, the same method can be done on an audio signal:
// allocate a buffer and write the random table into them
s.sendBundle(nil, ["/b_alloc", 144, 1, z.size, ["/b_setn", 144, 0, z.size] ++ z]);
// play a placeholder proxy
Ndef(\x).play;
// use the random values for sine frequency distribution
(
Ndef(\x, {
var inRand, outRand;
inRand = TRand.kr(0, 1.0, Impulse.kr(MouseY.kr(0.2, 400, 1)));
outRand = Shaper.kr(144, inRand);
SinOsc.ar(
LinExp.kr(outRand, 0, 1, 300, 7000)
) * 0.1
});
)
// or for colored noise:
(
Ndef(\x, {
var inRand, outRand;
inRand = WhiteNoise.ar;
outRand = Shaper.ar(144, inRand);
})
)
// to continue editing the distribution with the GUI left from above, use this function:
(
f = {
z = c.asRandomTable;
d.value = z;
s.sendBundle(nil, ["/b_setn", 144, 1, z.size] ++ z);
};
)
// end, clear variables
d = z = w = nil;
Ndef(\x).clear;
Tdef(\x).clear;
- more on interactive programmming with sc
- download code file: arbritrary_distribution_granular_example.rtf