MIDI cc values as array index for Array.fill

Hi,

while trying to dynamically create an array like this:

var posRatePulses = Array.fill(grainRateFwdRev, LFPulse.kr(0.1, 0, 0.1, grainOctaver, (grainRate)););

grainRateFwdRev is passed in as a MIDI CC coming from something like:
MIDIIn.control = {
arg src, chan, num, val;
switch ([chan, num],

[0, 19], {~x.set(\grainRateFwdRev, val)},
… }

I understand val is not an integer, still when applying the roundUp function and converting to an integer like this:

[0, 19], {~x.set(\grainRateFwdRev, val.roundUp.asInteger)},

I get the same error, which is “Index not an Integer”.

Anyone any idea on how to solve this?

Thanks and regards,
Dennis

You can’t dynamically create an array in a SynthDef.

Period.

You can create multiple SynthDefs with different sizes of array.

Or you can suppress the signals that depend on array elements you don’t want to use right now (by multiplying by a comparator, e.g. freqArray.collect { |freq, i| SinOsc.ar(freq) * (i < sizeSynthArg) }).

But you can’t make the server resize an array based on a control input.

Btw also we strongly recommend MIDIFunc or MIDIdef, never MIDIIn.

hjh

Actually, last night I didn’t read the code carefully enough.

In the specific example, it looks to me like an array isn’t necessary at all.

var posRatePulses = Array.fill(grainRateFwdRev, LFPulse.kr(0.1, 0, 0.1, grainOctaver, (grainRate)););

First, note the following difference:

Array.fill(10, rrand(1, 10));
-> [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]

Array.fill(10, { rrand(1, 10) });
-> [ 5, 7, 6, 3, 2, 6, 2, 3, 8, 2 ]

By omitting braces from the second part of Array.fill, it means that even if grainRateFwdRev were valid, you would get only n references to the same LFPulse, rather than n different LFPulses.

Assuming that you wanted n LFPulses, then… in this formulation, the pulses would all be the same, unless your (grainRate) is different per array iteration.

In any case, here is how I would do it.

(
~makeSynthDefForN = { |maxPartials = 10|
	SynthDef("pulsedemo" ++ maxPartials, { |out, gate = 1,
		num = 1, amp = 0.02,
		freq = 50, freqMul = 1.1,
		pulseRate = 5, pulseRateRand = 5
		|
		var pulses = Array.fill(
			maxPartials,  // not a synth input!
			{ |i|
				LFPulse.kr(pulseRate * (pulseRateRand ** Rand(-1.0, 1.0)),
					0, 0.1)
				* Lag.kr(i < num, 0.1)
			}
		);
		var f = freq;
		var oscs = Array.fill(maxPartials, {
			var osc = SinOsc.ar(f);
			f = f * freqMul;
			osc
		});
		var egs = Decay2.kr(pulses, 0.01, 0.08);
		// there is a weird bug with Splay vs .scramble
		// I shouldn't have to rearchitect Splay but... jeez...
		// var sig = Splay.ar((oscs * egs).scramble);
		var positions = Array.series(maxPartials, -1, 2 / (maxPartials - 1)).scramble;
		var sig = Pan2.ar(oscs * egs, positions, amp / maxPartials).sum;
		var mainEg = EnvGen.kr(Env.asr(0.01, 1, 0.1), gate, doneAction: 2);
		Out.ar(out, (sig * mainEg).dup);
	}).add;
};
)

~makeSynthDefForN.(50);

a = Synth("pulsedemo50", [num: 5, freq: 200, freqMul: 1.07, amp: 0.1]);

// MIDI would work just as well here
x = Slider(nil, Rect(800, 200, 150, 30)).front
.action_({ |view| a.set(\num, view.value * 50) });

a.free;

hjh

PS See Code markup: Correct style – it will help people to help you more efficiently.

Sure, creating an array and allocating memory in a high frequency is not really performant. Makes sense to me.

Maybe I should explain what I want to achieve. The idea behind my original approach is to have a value between 0-127 which would play a given rate for a GrainBuf instance in a calculated probability either forward or backwards (e.g.10% backw. /90% forward or 40% backwards and 60% forward). While playing grains backwards is achieved by negating the pitch, I thought I would dynamically fill an array with 127 times “1” or “-1” dependent on the ratio to then apply “choose” on the array which would get consumed by a Drand.

Sorry, in case my original question wasn’t concrete or verbose enough.

I think TWChoose will do what you want, without needing a large array at all.

TWChoose.kr(trig, [-1, 1], [reverseProb, 1 - reverseProb])

Or (TRand(0, 1, trig) - reverseProb).sign (which has a microscopic chance of producing a 0 rate multiplier but that’s incredibly unlikely).

hjh

TWChoose seems to be working well.


Dseq([TWChoose.kr(Impulse.ar(100), [grainRate * -1, grainRate * 1], [grainRateFwdRev, 1 - grainRateFwdRev])], inf),

Setting the trigger high enough seems to ensure that on each Deseq iteration a new evaluation on the 2 chooses is happening. Probably there’s a better way implementing this with some design or function that I don’t know yet, but it’s good enough for my needs as of now.

Thank you so much for your help!

Dseq is advancing based on a trigger, correct?

And TWChoose responds to triggers.

So… they could simply be the same trigger.

hjh

That‘s what I thought as well, but for some reason inserting the same trig value only worked with either very high or very low probability values (e.g. 90/10). Everything in-between e.g. 50/50 didn‘t have the desired effect.

Mathematically, if you’re generating a 50-50 probability and sampling it at random intervals, it should be identical to generating a 50-50 probability (or any probability).

(
var stream, next;
// baseline 50-50 probability
a = Pwrand([-1, 1], [0.5, 0.5], inf).asStream.nextN(10000);

// random sample 5% of the values
b = Array(10000);
stream = Pwrand([-1, 1], [0.5, 0.5], inf).asStream;

while { b.size < 10000 } {
	next = stream.next;
	if(0.05.coin) { b.add(next) };
};
)

a.count(_ > 0)  // 5062
b.count(_ > 0)  // 5084

… both very close to 50/50. I tried 80/20 as well, expected result.

So there must be something else wrong (or the desired effect is in fact not a 50-50 probability?).

hjh

My theory is that maybe Dseq might require a trigger at the exact same time or close to when it‘s triggered through GrainBuf or otherwise uses the previous value. Another theory is, that on each DSeq trigger the TswChoose is evaluated from scratch and the first choice always ends up on always the same with only one first trigger.

Am not familiar with the mechanics of the language yet so I‘m just guessing.

Will try to debug and figure out.