Allocate a buffer with numFrames depending on number of values

hey, im trying to digest an issue with a block of code and I have already spotted some of its problems but its difficult for me to share a simplified code example showing them all, so im trying to get there one by one.
The first problem is, Im declaring a function called ~makeMLP with creates an Environment and Im passing an array of parameter keys to this function. Inside the Environment should be a buffer allocated with the number of frames i have values for my keys im passing.
If these would just be single key value pairs, i could use params.size, but i would also like to pass array NamedControls.

so take this test SynthDef with array NamedControls:

(
SynthDef(\test, {
	var freqRange = \freqRange.kr([2500, 3500], spec: ControlSpec(100, 8000));
	var ringRange = \ringRange.kr([0.1, 0.15], spec: ControlSpec(0.1, 2.0));
	var ampRange = \ampRange.kr([0.1, 0.2], spec: ControlSpec(0.1, 1.0));
	var sig = Splay.ar(
		Array.fill(3, {
			Ringz.ar(
				Dust.ar(\dens.kr(5, spec: ControlSpec(1, 10))),
				exprand(freqRange[0], freqRange[1]),
				exprand(ringRange[0], ringRange[1]),
				exprand(ampRange[0], ampRange[1])
			)
		})
	).distort;
	sig = sig * \amp.kr(0.5, spec: ControlSpec(0, 1));
	Out.ar(\out.kr(0), sig);
}).add;
)

declare the simplified ~makeMLP function

(
~makeMLP = { |synthDefName, params|

	Environment.make { |self|

		~synthDef = SynthDescLib.global[synthDefName].def ?? {
			Error("SynthDef '%' not found".format(synthDefName)).throw
		};

		~params = params;
		if (params.isNil or: params.isEmpty) {
			Error("MLP: pass a non-empty array of parameter names to be controlled, % was given.".format(params)).throw
		};

		~nodeProxy = NodeProxy.audio(s, 2);
		~nodeProxy.prime(~synthDef);

		~getParamNums = { |self|
			self.params.collect{ |param|
				var val;
				val = self.nodeProxy.get(param);
				if (val.isArray) { val.size } { 1 };
			}.sum;
		};

		~bufParams = Buffer.alloc(s, self.getParamNums);

	}.know_(true);

};
)

and pass an array of keys to the function, where some of the keys have a corresponding array of values:

(
x = ~makeMLP.(\test,

	params: [
		\freqRange,
		\ringRange,
		\ampRange,
		\dens,
	],
);
)

i have created this ~getParamNums method, which cannot be evaluated in the Environment and also probably could be written in an easier way. for this specific example it should create a buffer with 7 frames. do you have some ideas on that? thanks

Does it help if you change it to this?

(
~makeMLP = { |synthDefName, params|

	Environment.make {
		var self = currentEnvironment;
		...
	}
};

self is passed in when you call a pseudo-method in an environment but this doesn’t mean that is passed into every function related to an environment. For make, it isn’t necessary to pass it because the new environment has been pushed – it’s accessible by currentEnvironment.

(FWIW the whole self thing for pseudo-methods, I never agreed with and I avoid this whole area in my own code.)

hjh

thanks, but by declaring var self = currentEnvironment; i still get the ERROR: Message ‘getParamNums’ not understood.

i have just been introduced to it and find it quite handy to test out functions and make different pseudo-methods available outside of the function, but it also made some trouble (most of my latest posts have been about that) so this maybe will change in the future.

Oh… it’s the know flag. You’re setting it after initialization, but you need it to be set before calling getNumParams.

(
e = Environment.make { |self|
	// oh ok, I was wrong, 'self' is passed in
	self.debug("self");
	
	~pseudoMethod = {
		"cheezburger".postln;
		3
	};
	
	self.know.debug("can I call pseudoMethods?");
	
	self.know = true;
	self.know.debug("NOW can I call pseudoMethods? pleaze?");
	
	self.pseudoMethod;
};
)

FWIW, with the ddwProto quark, I’d do it like this:

(
~mlpProto = Proto {
	// a real constructor!
	~prep = { |synthDefName, params|
		~synthDef = SynthDescLib.global[synthDefName].def ?? {
			Error("SynthDef '%' not found".format(synthDefName)).throw
		};

		~params = params;
		if (params.isNil or: params.isEmpty) {
			Error("MLP: pass a non-empty array of parameter names to be controlled, % was given.".format(params)).throw
		};

		~nodeProxy = NodeProxy.audio(s, 2);
		~nodeProxy.prime(~synthDef);

		// `this.aMethod` is just a function call
		~bufParams = Buffer.alloc(s, ~getNumParams.());
		
		currentEnvironment  // return 'this'
	};
	
	// Proto provides a hook for a proper destructor too
	~free = {
		~nodeProxy.clear;
		~bufParams.free;
	};

// edit: this definition is wrong;
// should look up in the SynthDef, oops
	~getNumParams = {
		~params.sum { |item| max(1, item.size) };
	};
};
)

(
~myMLP = ~mlpProto.copy.prep(\test,
	// no keyword args, though... a tradeoff
	[
		\freqRange,
		\ringRange,
		\ampRange,
		\dens,
	]
);
)

~myMLP.listVars
Variables:
[ bufParams, Buffer(0, 4, 1, 48000.0, nil) ]
[ nodeProxy, NodeProxy.audio(localhost, 2) ]
[ params, [ freqRange, ringRange, ampRange, dens ] ]
[ synthDef, SynthDef:test ]

~myMLP.free;

hjh

awesome its working, i have declared self.know = true; at the top of the Environment and used

		~getParamNums = { |self|
			self.params.collect{ |key|
				var val;
				val = self.nodeProxy.get(key);
				max(1, val.size);
			}.sum;
		};

to get the right number of frames for ~bufParams.

x.bufParams.numFrames` → 7

ddwProto looks interesting i will take a look!

is there a method available for SynthDef which is similiar to .get for NodeProxy, so i can pass a key and get the control value? If i use SynthDescLib.global[\test].def.controls i can just get a flat array of all the SynthDef controls but not for just the parameters i have passed as an argument to the function. Actually i would not even need the control values themselves, its just about the number of items for the parameters im passing.