Evaluating functions or values within a function

Hello, I am relatively new to SC, and having lots of fun with it. I am having trouble when using values that are meant to be evaluated later, within a function that is already being called. Specifically, in this method I am making for a custom class:

// quickly define response to noteOn MIDI events.
// name, chan, synthGroup, defaultAddAction and notesArray, are all instance variables
setNoteOn {|synthDef=\default, argsFunc, server=(Server.default), latency=0.05|
    //argsFunc processes de MIDI parameters as the user wishes, or with a default process
	argsFunc = if (argsFunc.isKindOf(Function)) {argsFunc} {ThyMIDIInterface.defaultSynthArgsFunc};
    //define a MIDIdef
	MIDIdef.noteOn(name++\On, {|vel, note_num, chan, src|
        //here is the problem. argsFunc should be called on noteOn events
		var args = argsFunc.value(vel, note_num, chan, src) ++ argsDict.asKeyValuePairs;
		var prev = notesArray[note_num];
		server.makeBundle(latency, {
			notesArray[note_num] = Synth(synthDef, args, synthGroup, defaultAddAction);
		});
		if (prev.canFreeSynth) {prev.release};
	}, chan: chan);
}

My idea is for this class to function as an interface where I can quickly define MIDI events as I use them commonly. However, when defining the MIDIdef, its function is called immediately instead of being deferred to when the noteOn event comes. I imagine I will run into this problem a lot in the future. Similarly, I want the MIDIdef to access instance variables (the argsDict) only when the noteOn event is received, and I’m sure I will run into the same problem.
How can I solve this issue?

Testing with a minimal version of your code:

(
MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.noteOn(\test, { |vel, num, chan, src|
  var args = [freq: 100].postln;
  s.makeBundle(0.05, {
    x = Synth(\default, args);
  });
})
)

MIDIIn.doNoteOnAction(0, 0, 64, 64)
x.free

The function within the MIDIdef is only called when there is a note on event (i.e. the argument array is posted and you hear a sound). Does this also work for you?

Also, why are you scheduling your Synth with some latency? Usually better for midi events to respond as quickly as possible…

Hi @tbanados !

a more similar example perhaps: still as @Eric_Sluyter suggests, the inner function is evaluated on each noteOn:

a = {|b| c +  b };
c = 3;
MIDIdef.noteOn(\foo,{|vel num| (a.value(9) + num).postln})
MIDIIn.doNoteOnAction(0, 0, 64, 64)

c=99;

MIDIIn.doNoteOnAction(0, 0, 64, 64)

Hello thank you for the answers, the latency was a misunderstanding on my part, it can be ignored. I followed your suggestions of simplifying the problem and managed to solve my issue.

Here is the solution for anyone else who may have trouble with this:
The issue occurred when calling the function argsFunc within the MIDIdef. I was mistaken in the cause of the error. My confusion was in thinking methods and functions worked in the same way, but in SC they are very different. Also, I was incorrectly nesting functions within functions when using an if statement to set the variable argsFunc.
Here is the minimal version of the class I am making:

Tester {
	*new {|...args|
		^super.new(*args)
	}

	*argsMethod {
		^{|vel, num|  [\amp, vel.linexp(1, 127, 0.1, 0.5), \freq, num.midicps]}
	}

	aMethod {|argsFunc|
		argsFunc = if (argsFunc.isKindOf(Function)) {{argsFunc}} {Tester.argsMethod};
		MIDIdef.noteOn(\test, { |vel, num, chan, src|
			var args;
			args = argsFunc.value(vel, num) ++ [\out, 0];
			Synth(\default, args);
		});
	}
}

In the line with the if statement, The class method argsMethod has to return a function (instead of using the method itself as the function), and when setting the variable argsFunc to itself in the if statement, double curly brackets must be used, or else the function will be immediatly evaluated, which is the issue I was having.
Thank you all for the help!

1 Like

You can also do this, which I think is the most common way:

Tester {
	classvar defaultArgsFunc;
	*initClass {
		defaultArgsFunc = { /*...*/ }
	}
	*new {|...args|
		^super.new(*args)
	}
	aMethod {|argsFunc|
		argsFunc = argsFunc ? defaultArgsFunc;
		/*...*/
	}
}

if you provide an *initClass method, it will be called once when the class library compiles, and is a good place to initialize class variables.

the ? returns argsFunc if it’s not nil, and otherwise returns defaultArgsFunc.
BTW there is a similar operator ?? which will immediately evaluate the second function if the first item is nil; you don’t want this here.

1 Like

And btw, re functions and methods

~method = Tester.findMethod(\aMethod);
~function = { 5.postln };

~method.class // -> Method
~method.ownerClass // -> Tester
~method.filenameSymbol

~function.class // -> Function
~function.def.sourceCode // -> "{ 5.postln }"



~tester = Tester();
~tester.perform(\aMethod);
~tester.aMethod; // same as above

~function.value;
~function.(); // same as above

inside a function, you can use thisFunction and inside a method, thisMethod

a function will always return the result of the last evaluated expression, a method explicitly returns with ^ (and if no explicit return will return the object it belongs to)

and, a function supports the programming concept of closures:

~function = { |x| { |y| x + y } };
~closure = ~function.(4);
~closure.(5);

maybe a good intuition about methods vs functions is that methods are primarily “verbs” acting on or with their objects, while functions are more like “nouns” (objects themselves) encapsulating a stored action…

1 Like

The doubled braces should definitely not be necessary.

If argsFunc is a function, then { argsFunc } is a function that returns the other function (which is what you want).

(
f = { |func|
	func = if(func.isFunction) {
		func
	} {
		{ |a| a * 2 }
	}
};
)

g = f.({ |a| a * 2 + 1 });  // result: a Function

g.(3)  // 7

If f were evaluating argsFunc in that if, it would stop with an error.

Now, if you wrote if(argsFunc.isFunction, argsFunc, Tester.argsMethod), then it would execute argsFunc as the true branch.

hjh

Thank you everyone for all your help! I was definitely needing a better understanding of the language before continuing on this stuff.

1 Like