View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide

creating an arbitrary random distribution

Home   How To   Code Pool   Public Library   Theory   Events
there is several ways to achieve a certain given random distribution.
here two methods are shown, whereby the last one is the only reliable one
in terms of efficiency.



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:

Uploaded Image: distribut.gif



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;



Links to this Page