How to scramble a channels array on the server?

Hey everyone,
Inside a synth I’m generating a 16-channel array and playing it… i.e. something like this:

sig = Saw.ar((1..16) * 100);
Splay.ar(sig);

I would like to be able to use a trigger to scramble the channel order of sig before sending it to Splay, in the same way one can do this in the language using ArrayedCollection.scramble

Any clue how I might do this? I was looking at Demand rate UGens like Drand and Dshuf, but it looks like these will only give you one channel out of the array rather than scrambling the whole thing.

xxjc

The trick is to feed one Dshuf into multiple Demand units. This accesses all of the Dshuf values at once.

(
SynthDef(\rearrange, { |freq = 440, amp = 0.1, gate = 1, out = 0|
	var eg = EnvGen.kr(Env.adsr, gate, doneAction: 2);
	
	var n = 5;
	var initTrig = Impulse.kr(0);
	var randomizer = Dshuf((0 .. n-1), 1);
	var indices = Demand.kr(Array.fill(n, initTrig), 0, randomizer);
	
	var freqs = freq * (1..n);
	var oscs = SinOsc.ar(freqs);
	var channels = Select.ar(indices, oscs);
	
	Out.ar(out, Splay.ar(channels, level: amp) * eg)
}).add;
)

(instrument: \rearrange, sustain: 1.5).play;

hjh

1 Like

there is also TScramble:


(
SynthDef(\rearrange, { |freq = 440, amp = 0.1, gate = 1, out = 0|
	var eg = EnvGen.kr(Env.adsr, gate, doneAction: 2);
	
	var n = 5;
	var initTrig = Impulse.kr(0);
	
	var freqs = freq * (1..n);
	var oscs = SinOsc.ar(freqs);
	var channels = TScramble.ar(oscs, initTrig);
	
	Out.ar(out, Splay.ar(channels, level: amp) * eg)
}).add;
)
1 Like

Oh that’s cool… worth noting that this is in the wslib quark (not a core or sc3-plugins class).

hjh

1 Like

Hmm… I spoke too soon. The indices are only scrambled once (even with a non-0 frequency Impulse). Is it possible to scramble the indices again and again after the synth has been initialized?

Ndef(\monitorme, {
	var n = 5, amp = 0.1, freq=200, sig;
	var t_trigs = Impulse.kr(2).dup(n);
	var randomizer = Dshuf((0 .. n-1), inf);
	var indices = Demand.kr(t_trigs, 0, randomizer);
	var freqs = freq * (1..n);
	var oscs = SinOsc.ar(freqs);
	var channels = Select.ar(indices, oscs);
	sig = Splay.ar(channels, level: amp);
	indices.poll(1);
	sig;
}).play(outbus: 0);

Yes, that’s correct. Pshuf / Dshuf intentionally repeats the same ordering.

“Why? Wouldn’t you want a new ordering on each repeat?” – If it enforced a new ordering on every repeat, then it would be impossible to repeat the same ordering. So this design gives you a choice.

To get the new ordering, Pshuf / Dshuf should output one ordering only one time, and then repeat this externally. That is, there’s a difference between a Pshuf / Dshuf repeating infinitely, and infinitely repeating a one-shot Pshuf / Dshuf.

Pshuf([1, 2, 3], 3).asStream.all
-> [ 3, 2, 1, 3, 2, 1, 3, 2, 1 ]

Pn(Pshuf([1, 2, 3], 1), 3).asStream.all
-> [ 1, 3, 2, 2, 3, 1, 2, 1, 3 ]

s.boot;

(
a = {
	var trig = Impulse.kr(4);
	var source = Dshuf([1, 2, 3], 3);
	var demand = Demand.kr(trig, 0, source);
	demand.poll(trig);
	FreeSelfWhenDone.kr(demand);
	Silent.ar(1)
}.play;
)

UGen(OutputProxy): 3
UGen(OutputProxy): 2
UGen(OutputProxy): 1
UGen(OutputProxy): 3
UGen(OutputProxy): 2
UGen(OutputProxy): 1
UGen(OutputProxy): 3
UGen(OutputProxy): 2
UGen(OutputProxy): 1
UGen(OutputProxy): 1  // not sure why there's an extra

There’s no Dn() so we have to translate Pn(x, n) as Dseq([x], n).

(
a = {
	var trig = Impulse.kr(4);
	var source = Dseq([Dshuf([1, 2, 3], 1)], 3);
	var demand = Demand.kr(trig, 0, source);
	demand.poll(trig);
	FreeSelfWhenDone.kr(demand);
	Silent.ar(1)
}.play;
)

UGen(OutputProxy): 2  // 213
UGen(OutputProxy): 1
UGen(OutputProxy): 3
UGen(OutputProxy): 3  // 312 -- changed ordering
UGen(OutputProxy): 1
UGen(OutputProxy): 2
UGen(OutputProxy): 2  // 213 -- changed again
UGen(OutputProxy): 1
UGen(OutputProxy): 3
UGen(OutputProxy): 3

hjh

1 Like

Hey there,

I recently came across a similar problem (but I needed the scramble to be deterministic). Here’s what I came up with:

(
var permute = {|array, permutation=0|
	var size = array.size;
	var selectIndices, res;
	var func = {|coll, size|  (size+1).collect{|i| (coll ++ size).rotate(i)  } };
	var factorial = size.asFloat.factorial;
	var divs = (2..size-1).collect{|layer| (layer..size).product.div(layer) } ++ [ 1 ];
	var indexes2 = (2..size).collect{|layer,index| permutation.div(divs[index]).trunc.mod(layer)};
	selectIndices = [[0]];
	(size-1).do{|layer| selectIndices = Select.ar(indexes2[layer], K2A.ar( func.(selectIndices, layer+1))).flatten };
	Select.ar(selectIndices, array);
};

var scramble = {|array, in|
	permute.(array, Hasher.ar(in).range(0, array.size.factorial));
};

SynthDef(\permute, {
	var phasor = Phasor.ar(0, SampleDur.ir*0.5, 0, 1);
	var sequence = (0..7) * 150; 
	// permute array
	sequence = permute.(K2A.ar(sequence), \permute.ar(0));
	sequence = Select.ar(phasor.linlin(0,1, 0, sequence.size).trunc, K2A.ar(300 + sequence));
	Out.ar(0,0.2 * SinOsc.ar(sequence));
	// Changed.ar(phasor.trunc(1/5), 0)
}).add;

SynthDef(\scramble, {
	var phasor = Phasor.ar(0, SampleDur.ir*0.5, 0, 1);
	var sequence = (0..7) * 150; 
	// permute array
	sequence = scramble.(K2A.ar(sequence), \scramble.ar(0));
	sequence = Select.ar(phasor.linlin(0,1, 0, sequence.size).trunc, K2A.ar(300 + sequence));
	Out.ar(0,0.2 * SinOsc.ar(sequence));
	// Changed.ar(phasor.trunc(1/5), 0)
}).add;

SynthDef(\multichanscramble, { 
	var pitches = (1..8) * 100; 
	pitches = scramble.(K2A.ar(pitches), \scramble.ar);
	Out.ar(0, Splay.ar(SinOsc.ar(pitches, Array.series(8, 0.125, 0.125) )) * 0.2);
}).add;
)

// permutations: range is 0..array.size.factorial
x = Synth(\permute)
x.set(\permute, 0)
x.set(\permute, 12)
x.set(\permute, 14)
x.set(\permute, 23)
x.set(\permute, 4)
x.set(\permute, 7.factorial.rand)
x.free

x = Synth(\scramble)
x.set(\scramble, 0)
x.set(\scramble, 1)
x.set(\scramble, 2)
x.set(\scramble, 3)
x.set(\scramble, 4)
x.set(\scramble, 5)
x.set(\scramble, 7.factorial.rand.postln)

x = Synth(\multichanscramble)
x.set(\scramble, 0)
x.set(\scramble, 1)
x.set(\scramble, 2)
x.set(\scramble, 3)
x.set(\scramble, 4)
x.set(\scramble, 5)
x.set(\scramble, 8.factorial.rand.postln

I’m by no means experienced in this field, but it seems like this is a robust way to get all the permutations:

(
s.options.numWireBufs = 64 * 64;
s.options.sampleRate_(48000);
s.options.blockSize_(8);
s.reboot
)
(
var size = 5;
var synth = Synth.basicNew(\permute, s, s.nextNodeID).register;
SynthDef(\permute, {
	var arr = [[0]], res;
	var func = {|coll, size|  (size+1).collect{|i| (coll ++ size).rotate(i)  } };
	var factorial = size.asFloat.factorial;
	var perm = Phasor.ar(0, 1/BlockSize.ir, 0, factorial);
	var divs = (2..size-1).collect{|layer| (layer..size).product.div(layer) } ++ [ 1 ];
	var indexes = (2..size).collect{|layer,index| perm.div(divs[index]).trunc.mod(layer)};
	arr = [[0]];
	(size-1).do{|layer| arr = Select.ar(indexes[layer], K2A.ar( func.(arr, layer+1))).flatten };
	SendReply.ar(Changed.ar(perm.trunc) + Impulse.ar(0), "/proof", arr);
	FreeSelf.kr(DelayN.ar(Trig.kr( Slope.ar(perm).abs > 10000), 1, 1));
}).store(completionMsg: synth.newMsg );
p = Set();
synth.onFree({ "set size: %, factorial: %, test outut is %".format(p.size, size.asFloat.factorial, p.size == size.asFloat.factorial).postln});
OSCdef(\proof, {|msg| p.add(msg[3..].collect(_.asInteger))}, "/proof");
)

On my machine, I can run this with arrays up to a size of about 20 or so. It’s surprisingly easy on the CPU, but of course a bit intense memory-wise.

All best,
moritz

I have just added Durn to my library. Release GrainUtils v1.3.6 · dietcv/GrainUtils · GitHub
It draws a card from a deck and makes sure every card is only picked once before every card has been drawn from the deck. It also makes sure that the first card of the current deck doesnt match the last card of the previous deck to prevent intercycle repetition of values. With the chance parameter you can set the probability for every draw to shuffle the deck. So you get either repeating cycles of non repeating values for chance 0 or the probability of change for each of these values per draw for chance bigger 0.