Sorting dynamically generated arrays

Hello list,

is there any workaround to sorting a dynamically generated array within a synthdef?
E.g. in this case by returning an array in which the random values are sorted by ascending order?

{TRand.kr(0,1.0,Impulse.kr(1!8)).poll(1)}.play

Thanks,
Jan

Poll sends data directly to the post window, and to date there is no way to record or retrieve any data sent to it.

Hi @Rainer, im not sure i follow! what i mean is sorting the generated array dynamically within the synthdef (independently of the post window)…

@mousaique

I would also like to know if it is possible on the server.
What I could suggest is sending the array to sclang, then the sclang sorts it and resend it to the synth as follows:

(
fork{
	x = { 
		var interval = 10;
		var trig = Impulse.kr(interval);
		var randoms = TRand.kr(0, 1.0, trig!3);
		SendReply.kr(trig, '/randoms', randoms.poll(interval, label: "sending"));
		\sortedRandoms.kr(0!3).poll(interval, label: "received")
	}.play;
	
	OSCdef(\listener, { |msg|
		var randoms = msg[3..];
		randoms.sort;
		x.set(\sortedRandoms, randoms.postln)
	}, '/randoms')
}
)

Evaluating this code block returns:

→ a Routine
sending: 0.788379
sending: 0.291889
sending: 0.681298
received: 0
received: 0
received: 0
[ 0.29188907146454, 0.68129765987396, 0.78837931156158 ]
sending: 0.934453
sending: 0.946488
sending: 0.0596933
received: 0.291889
received: 0.681298
received: 0.788379
[ 0.059693336486816, 0.93445289134979, 0.94648826122284 ]
sending: 0.227502
sending: 0.955602
sending: 0.125988
received: 0.0596933
received: 0.934453
received: 0.946488
[ 0.12598848342896, 0.22750234603882, 0.95560169219971 ]

hi @prko, thanks for your suggestion!! that could surely be a workaround but i still do wonder if its actually impossible to do on the server…

A SynthDef is a static (unchanging) network of UGens.

Sorting in a static graph is at best inefficient – it won’t scale up to large arrays.

Consider what it takes just to find the smallest value:

var sig = TRand.kr(0,1.0,Impulse.kr(1!8);

var min0 = sig[0];
sig.doAdjacentPairs { |c0, c1| min0 = min(min0, c1) };

For your 8-element array, that’s min(min(min(min(min(min(min(sig[0], sig[1]), sig[2]), sig[3]), sig[4]), sig[5]), sig[6]), sig[7]). I’m not sure that a better way even exists.

Next problem… how to get the second smallest? Note that min0 < second isn’t a sufficient condition, because the array’s smallest value could be duplicated – but min0 <= second would just re-identify the same as min0!

So it gets tricky.

It would definitely be O(n^2) i.e. slow for large datasets, not scalable.

My guess is that it would be better to put the values into a buffer, then add a plugin to sort buffer contents (upon a trigger maybe?) – because sorting is easy in C, but hard in signal graphs – then read the buffer values back out into separate channels.

hjh

thanks james, yes, i see the problem this could amount to.
regarding your suggestions, what plugin do you have in mind to sort the data in the buffer?

As far as I know, it doesn’t exist. Someone would have to write it.

hjh

oh, i see thanks james!

@mousaique
I do not know what you intend. However, I made the following code for my test and have thought this thread could be related to this code draft:

(
s.waitForBoot{
	var window, view, synth;
	window = Window("FreqScopeViews", Rect(0, 0,  368, 440));
	view = View(window,
		Rect(0, 0, window.bounds.width, window.bounds.height));
	{ |i|
		a = StaticText(view, Rect(3, i * 220, 362, 20))
		.string_(["Left", "Right"][i]);
		FreqScopeView(view, Rect(3, i * 200 + (i + 1 * 20), 362, 200))
		.active_(true)
		.freqMode_(1)
		.inBus_(i)}!2;
	window.front;
	synth = { |t_gate|
		var interval = 1/3;
		var trig = Impulse.kr(interval);
		var randoms = TRand.kr(54, 60, trig!2) + [0, 30];
		SendReply.kr(trig, '/randoms', randoms.poll(interval, label: "sending"));
		SinOsc.ar(
			(
				\sortedRandoms.kr([0!2]).poll(interval*10, label: "received")
				+.t[0, 3, 7, 9, 11, 14, 18, 20]
			)
			.midicps)
		* 0.01
		* EnvGen.kr(Env.perc, t_gate)
	}.play;
	
	OSCdef(\listener, { |msg|
		var randoms = msg[3..];
		randoms.sort;
		synth.set(\sortedRandoms, randoms.postln, \t_gate, 1)
	}, '/randoms')
}
)

The previous code has rhythmic accuracy problems. Thus, I changed the code to the code below, but it still has rhythm issues. What does cause rhythmic instability?

(
s.waitForBoot{
	var window, view, synth;
	window = Window("FreqScopeViews", Rect(0, 0,  368, 440));
	view = View(window,
		Rect(0, 0, window.bounds.width, window.bounds.height));
	{ |i|
		a = StaticText(view, Rect(3, i * 220, 362, 20))
		.string_(["Left", "Right"][i]);
		FreqScopeView(view, Rect(3, i * 200 + (i + 1 * 20), 362, 200))
		.active_(true)
		.freqMode_(1)
		.inBus_(i)}!2;
	window.front;
	
	SynthDef(\sendReplyTest, { |t_gate|
		var interval = 1/3;
		var trig = Impulse.kr(interval);
		var randoms = TRand.kr(54, 60, trig!2) + [0, 30];
		var sig = SinOsc.ar(
			(
				\sortedRandoms.kr([0!2])
				.poll(interval * 3, label: "received")
				+.t[0, 3, 7, 9, 11, 14, 18, 20]
			)
			.midicps)
		* 0.01
		* EnvGen.kr(Env.perc, t_gate);
		SendReply.kr(trig, '/randoms', randoms.poll(interval, label: "sending"));
		OffsetOut.ar(0, sig)
	}).add;
	
	s.sync;
	
	s.makeBundle(0.2, {
		synth = Synth(\sendReplyTest) });
	
	OSCdef(\listener, { |msg|
		var randoms = msg[3..];
		randoms.sort;
		synth.set(\sortedRandoms, randoms.postln, \t_gate, 1)
	}, '/randoms')
}
)

Since TRand is uniform in its randomness, meaning it is equally likely to choose any number within the range, you should be able to get something approximate by doing this:

{
	var trig = Impulse.kr(1!8);
	var rands = trig.collect{|t, n|
		var steps = trig.size;
		var lo = n / (steps);
		var hi = (n+1) /(steps);
	
		TRand.kr(lo,hi,t)
	};
    rands.poll(1);
}.play

That is, splitting the full range into smaller subsections and choosing random values. For small array sizes this will be inaccurate, but as you increase the array size it should approach what you wanted.

For lower values you could add some thing like this to make it even more accurate:


{
	var trig = Impulse.kr(1!8);
	var low = TRand.kr(0, 0.2, trig[0]);
	var high = TRand.kr(0.8, 1, trig[0]);

	var rands = trig.collect{|t, n|
		var steps = trig.size;
		var lo= n / (steps);
		var hi = (n+1) /(steps);
	
		TRand.kr(lo,hi,t)
	}.linlin(0,1, low, high);
	rands.poll(1);
}.play

Hi, thanks for the suggestion! While i used a TRand for the example for simplicitys sake im actually aiming to sort non-uniform distributions so unfortunately this wont do it…