Choose message in Array_series' step argument

Hello all,

I’d like to choose different elements from a list every time an Array.series’ step argument creates a next element.

With rrand() this is easy:

(
~list = Array.series(
	size: 10,
	start: 100,
	step: 100*{1+rand2(0.1)}
);
~test = Pbind(
	\dur, 0.25,
	\freq, Pseq(~list,2)
).play;
)

The distance between every element in ~list will be 100 and a deviation of + or - maximum 10%. But at each distance the deviation will be slightly different.

I’d like to replicate this with .choose, ie.:

(
~mrL = [0,3,7].midiratio;
~list = Array.series(
	size: 10,
	start: 100,
	step: 100*{~mrL.choose}
);
~test = Pbind(
	\dur, 0.25,
	\freq, Pseq(~list,2)
).play;
)

So that with every step up, the distance will be either 100 + unison or 100 + third or 100 + fifth.

However, in this case SC 3.12.2 returns an error message:

ERROR: Message 'choose' not understood.
RECEIVER:
   nil
ARGS:

PROTECTED CALL STACK:
	Meta_MethodError:new	0x1170d1600
		arg this = DoesNotUnderstandError
		arg what = nil
		arg receiver = nil
	Meta_DoesNotUnderstandError:new	0x1170d3940
		arg this = DoesNotUnderstandError
		arg receiver = nil
		arg selector = choose
		arg args = [  ]
	Object:doesNotUnderstand	0x11657a280
		arg this = nil
		arg selector = choose
		arg args = nil
	BinaryOpFunction:valueArray	0x117407a80
		arg this = a BinaryOpFunction
		arg args = [  ]
	BinaryOpFunction:valueArray	0x117407a80
		arg this = a BinaryOpFunction
		arg args = [  ]
	BinaryOpFunction:value	0x117407600
		arg this = a BinaryOpFunction
		arg args = nil
	a FunctionDef	0x1188070c0
		sourceCode = "#{
					~freq.value + ~detune
				}"
	a FunctionDef	0x118818a40
		sourceCode = "#{|server|
						var freqs, lag, strum, sustain;
						var bndl, addAction, sendGate, ids;
						var msgFunc, instrumentName, offset, strumOffset;

						// var schedBundleArray;

						freqs = ~detunedFreq.value;

						// msgFunc gets the synth's control values from the Event
						msgFunc = ~getMsgFunc.valueEnvir;
						instrumentName = ~synthDefName.valueEnvir;

						// determine how to send those commands
						// sendGate == false turns off releases

						sendGate = ~sendGate ? ~hasG...etc..."
		arg server = localhost
		var freqs = nil
		var lag = nil
		var strum = nil
		var sustain = nil
		var bndl = nil
		var addAction = nil
		var sendGate = nil
		var ids = nil
		var msgFunc = nil
		var instrumentName = nil
		var offset = nil
		var strumOffset = nil
	a FunctionDef	0x118816e00
		sourceCode = "#{
					var tempo, server, eventTypes, parentType;

					parentType = ~parentTypes[~type];
					parentType !? { currentEnvironment.parent = parentType };

					server = ~server = ~server ? Server.default;

					~finish.value(currentEnvironment);

					tempo = ~tempo;
					tempo !? { thisThread.clock.tempo = tempo };


					if(currentEnvironment.isRest.not) {
						eventTypes = ~eventTypes;
						(eventTypes[~type] ?? { eventTypes[\\note] }).value(server)
					};

					~callback.value(current...etc..."
		var tempo = nil
		var server = localhost
		var eventTypes = ( 'fadeBus': a Function, 'freeAllocWrite': a Function, 'tree': a Function, 'on': a Function, 
  'load': a Function, 'freeBuffer': a Function, 'group': a Function, 'freeAllocRead': a Function, 'allocWrite': a Function, 
  'cue': a Function, 'grain': a Function, 'Synth': a Function, 'freeAllocWriteID': a Function, 'alloc': a Function, 
  'rest': a Function, 'sine2': a Function, 'sine1': a Function, 'midi': a Function, 'set': a Function, 
  'setProperties': a Function, 'parGroup': a Function, 'allocRead': a Fu...etc...
		var parentType = nil
	a FunctionDef	0x1187b8000
		sourceCode = "<an open Function>"
	Function:prTry	0x117397740
		arg this = a Function
		var result = nil
		var thread = a Routine
		var next = nil
		var wasInProtectedFunc = false
	
CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	Nil:handleError
		arg this = nil
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of DoesNotUnderstandError>
	Object:throw
		arg this = <instance of DoesNotUnderstandError>
	Function:protect
		arg this = <instance of Function>
		arg handler = <instance of Function>
		var result = <instance of DoesNotUnderstandError>
	Environment:use
		arg this = <instance of Event>
		arg function = <instance of Function>
		var result = nil
		var saveEnvir = <instance of Environment>
	Event:play
		arg this = <instance of Event>
	Event:playAndDelta
		arg this = <instance of Event>
		arg cleanup = <instance of EventStreamCleanup>
		arg mute = false
	EventStreamPlayer:prNext
		arg this = <instance of EventStreamPlayer>
		arg inTime = 10720.126477272
		var nextTime = nil
		var outEvent = <instance of Event>
	< FunctionDef in Method EventStreamPlayer:init >
		arg inTime = 10720.126477272
	Routine:prStart
		arg this = <instance of Routine>
		arg inval = 10720.126477272
^^ ERROR: Message 'choose' not understood.
RECEIVER: nil

This issue - to my understanding - can be resolved with Array.fill:

(
~mrL = [0,3,7].midiratio;
~list = Array.fill(
	size: 10,
	function: { |i| i+1*100*~mrL.choose});
~test = Pbind(
	\dur, 0.25,
	\freq, Pseq(~list,2)
).play;
)

However, I was wondering if this can be achieved with Array.series in a simple/concise way?

Thanks,
cd

If you drop the curly braces around ~mrL.choose it works:

(
~mrL = [0,3,7].midiratio;
~list = Array.series(
size: 10,
start: 100,
step: 100 * ~mrL.choose
);
~test = Pbind(
\dur, 0.25,
\freq, Pseq(~list,2)
).play;
)

This is a more pattern-idiomatic way and much easier to write/understand/maintain:

(
~test = Pbind(
    \dur, 0.25,
    \freq, Pseries(
        start: 100,
        step: Prand([0,3,7].midiratio, inf),
        length: 10
    )
).play;
)

That is, Pseries is designed for lazy selection of the step size (different step per output value), and Array.series is not designed for that. (If I need to generate such an array all at once, instead of step by step in a Pbind, I’ll actually write Pseries(... parameters...).asStream.nextN(size) rather than try to hack Array.series.)

The Array.series with function fails in the pattern because it’s evaluating the function within the event, so it doesn’t have access to ~mrL.

I think the Array.series here might not even return correct results. Maybe I’ll check that later, when I’m at the computer.

hjh

Indeed, watch this:

a = Array.series(10, 0, { rrand(1, 4) });
-> [a BinaryOpFunction, a BinaryOpFunction, a BinaryOpFunction, a BinaryOpFunction, a BinaryOpFunction, a BinaryOpFunction, a BinaryOpFunction, a BinaryOpFunction, a BinaryOpFunction, a BinaryOpFunction]

Since they’re functions, let’s value them and see what the results are.

b = a.collect(_.value);
-> [0, 4, 8, 12, 16, 15, 12, 28, 32, 9]

Starting off OK, but then… 16, 15, 12… decreasing values even though step is specified as positive 1, 2, 3 or 4. 12 to 28 and 32 to 9 are also amusing.

Also, without changing a:

a.last.value;
-> 9

a.last.value;
-> 36

a.last.value;
-> 18

So what you’ve really got is not a series of randomly chosen increments, but rather an array of functions, each one calculating as some multiple of the array index: Array.fill(10, { |i| { i * rrand(1, 4) } }).

Array.series is just the wrong hammer for this.

(Also, did you mean geom? Frequency ratios should be multiplied, not added, unless you’re looking for the intervals to get smaller as you go higher.)

hjh

Your suggestion seem to select one element from the ~mrL list then uses that one selected value for the step parameter of the Pseries.
See here:

(
~mrL = [0,3,7];
~list = Array.series(
size: 4,
start: 100,
step: 100*~mrL.choose
);
~test = Pbind(
\dur, 0.25,
\freq, Pseq(~list,2).trace
).play;
)

The .choose message in the Pseries seem to return either:

[100,100,100,100]; // step: 100 * 0
[100,400,700,1000]; // step: 100 * 3
[100,800,1500,2200]; // step: 100 * 7

What I would’ve like the .choose message to do is to randomly select an item from ~mrL at each increment. So - given this latter example - something like this:

[
	100,
	400, // 1st step: 100 * 3;
	1100, // 2nd step: 100 * 7;
	1100 // 3rd step: 100 * 0
];

Best,
cd

This is great, I should’ve thought of this option! :slight_smile:

Oh wow, interesting, thank you for looking into this and testing it!

What I wanted is a series of increasing frequencies with either 100 and unison or 100 and third or 100 and fifth intervals up each time. So instead of

[
	100, 
	100 * 3.midiratio, 
	100 * (3 + 7).midiratio, 
	100 * (3 + 7 + 0).midiratio,
	etc.
]

something like this:

[
	100, 
	200 * 3.midiratio, 
	300 * (3 + 7).midiratio, 
	400 * (3 + 7 + 0).midiratio,
	etc.
]

So increments of: overtones AND randomly selected unison/third/fifth interval. Modifying your example accordingly:

(
~test = Pbind(
    \dur, 0.25,
    \freq, Pseries(
        start: 100,
		step: 100*Prand([0,3,7].midiratio, inf),
        length: 10
    ).trace
).play;
)

Thank you for your solution!

Best,
cd

So you have the product of two series… which may be written as the product of two series.

I’ll add a reset into each, since the frequencies are likely to go stratospheric pretty quickly. (The interval series’ most extreme case is perfect 5th every time, where 12 steps will hit 7 octaves.)

Pn(Pseries(100, 100, { rrand(5, 12) }), inf)
*
Pn(Pseries(0, Prand([0, 3, 7], inf), { rrand(3, 7) }), inf).midiratio

I suspect that this one – Pseries(100, 100*Prand([0,3,7].midiratio, inf), 10) – is probably not doing exactly what you specified, since addition and multiplication can’t be arbitrarily recombined (they’re not associative).

hjh